Add features to GUI (#757)
* controller image changes depending on the selected controller type the new controller image assets are temporary until i get new ones * Game list scans subdirs for games * Key file existence check * Only shows Program NCAs in Application list * Change shown GUI columns without restarting * Sort by column if you click on the column header Columns are sorted as text so there are inaccuracies on some columns * Fix sort on Time Played, Last Played and File Size columns * Add ability to designate favourite games #1 TODO: - Make fav games persistent - Fix invisible check marks due to theme * Add ability to designate favourite games #2 Also removed default theme * Added a Windows specific build condition and a Linux bug fix * bugfix * Load metadata from JSONs * Temp bug fix for MacOS * lil clean up * requested changes * Misc fixes * edited schema and config * Show the TitleID of games on the title bar * gui column config option have names * Async loading of game list * bugfix and cleanup * thog's requested changes * requested changes and cleanup still need to fix the gtk seizure * Fix issue where an ExeFS as a NSP didn't show up in the application list * Minor fixes * catch glib unhandled exceptions * Make sure to do UI manipulation in the main thread * Print path of invalid files * Ac_k's requested changes * Return of the dark theme * move AboutInfo struct to another file * sort usings * changes - gdkchan's requested changes that have been marked resolved - made some structs internal as they aren't used outside of the GUI - renamed Ryujinx.UI to Ryujinx.Ui to fit naming convention and folder structure - fixed bug where controller type dropdown box is stretched
@ -105,11 +105,9 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
public Nacp ControlData { get; set; }
|
||||
|
||||
public string CurrentTitle { get; private set; }
|
||||
|
||||
public string TitleName { get; private set; }
|
||||
|
||||
public string TitleID { get; private set; }
|
||||
public string TitleId { get; private set; }
|
||||
|
||||
public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
|
||||
|
||||
@ -366,7 +364,7 @@ namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
ControlData = new Nacp(controlFile.AsStream());
|
||||
|
||||
TitleName = CurrentTitle = ControlData.Descriptions[(int) State.DesiredTitleLanguage].Title;
|
||||
TitleName = ControlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
|
||||
}
|
||||
}
|
||||
|
||||
@ -500,12 +498,12 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
Nacp controlData = new Nacp(controlFile.AsStream());
|
||||
|
||||
TitleName = CurrentTitle = controlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
|
||||
TitleID = metaData.Aci0.TitleId.ToString("x16");
|
||||
TitleName = controlData.Descriptions[(int)State.DesiredTitleLanguage].Title;
|
||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(CurrentTitle))
|
||||
if (string.IsNullOrWhiteSpace(TitleName))
|
||||
{
|
||||
TitleName = CurrentTitle = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
TitleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
}
|
||||
|
||||
return controlData;
|
||||
@ -517,7 +515,7 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
else
|
||||
{
|
||||
TitleID = CurrentTitle = metaData.Aci0.TitleId.ToString("x16");
|
||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
||||
}
|
||||
}
|
||||
|
||||
@ -557,7 +555,7 @@ namespace Ryujinx.HLE.HOS
|
||||
}
|
||||
}
|
||||
|
||||
TitleID = CurrentTitle = metaData.Aci0.TitleId.ToString("x16");
|
||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
||||
|
||||
LoadNso("rtld");
|
||||
LoadNso("main");
|
||||
@ -659,8 +657,8 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
ContentManager.LoadEntries();
|
||||
|
||||
TitleName = CurrentTitle = metaData.TitleName;
|
||||
TitleID = metaData.Aci0.TitleId.ToString("x16");
|
||||
TitleName = metaData.TitleName;
|
||||
TitleId = metaData.Aci0.TitleId.ToString("x16");
|
||||
|
||||
ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
||||
|
||||
return new ApplicationLaunchProperty
|
||||
{
|
||||
TitleId = BitConverter.ToInt64(StringUtils.HexToBytes(context.Device.System.TitleID), 0),
|
||||
TitleId = BitConverter.ToInt64(StringUtils.HexToBytes(context.Device.System.TitleId), 0),
|
||||
Version = 0x00,
|
||||
BaseGameStorageId = (byte)StorageId.NandSystem,
|
||||
UpdateGameStorageId = (byte)StorageId.None
|
||||
|
@ -3,7 +3,7 @@ using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
class Aci0
|
||||
public class Aci0
|
||||
{
|
||||
private const int Aci0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24;
|
||||
|
||||
|
@ -3,7 +3,7 @@ using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
class Acid
|
||||
public class Acid
|
||||
{
|
||||
private const int AcidMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24;
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
class FsAccessControl
|
||||
public class FsAccessControl
|
||||
{
|
||||
public int Version { get; private set; }
|
||||
public ulong PermissionsBitmask { get; private set; }
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
class KernelAccessControl
|
||||
public class KernelAccessControl
|
||||
{
|
||||
public int[] Capabilities { get; private set; }
|
||||
|
||||
|
@ -7,7 +7,7 @@ namespace Ryujinx.HLE.Loaders.Npdm
|
||||
// https://github.com/SciresM/hactool/blob/master/npdm.c
|
||||
// https://github.com/SciresM/hactool/blob/master/npdm.h
|
||||
// http://switchbrew.org/index.php?title=NPDM
|
||||
class Npdm
|
||||
public class Npdm
|
||||
{
|
||||
private const int MetaMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24;
|
||||
|
||||
|
@ -5,7 +5,7 @@ using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Npdm
|
||||
{
|
||||
class ServiceAccessControl
|
||||
public class ServiceAccessControl
|
||||
{
|
||||
public IReadOnlyDictionary<string, bool> Services { get; private set; }
|
||||
|
||||
|
@ -4,9 +4,15 @@
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ASET/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Astc/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Luma/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mins/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=nacp/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Npad/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=patreon/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Probs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ryujinx/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Sint/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Snorm/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Srgb/@EntryIndexedValue">True</s:Boolean>
|
||||
|
@ -7,7 +7,9 @@
|
||||
"logging_enable_error": true,
|
||||
"logging_enable_guest": true,
|
||||
"logging_enable_fs_access_log": false,
|
||||
"logging_filtered_classes": [ ],
|
||||
"logging_filtered_classes": [
|
||||
|
||||
],
|
||||
"enable_file_log": true,
|
||||
"system_language": "AmericanEnglish",
|
||||
"docked_mode": false,
|
||||
@ -15,12 +17,27 @@
|
||||
"enable_vsync": true,
|
||||
"enable_multicore_scheduling": true,
|
||||
"enable_fs_integrity_checks": true,
|
||||
"fs_global_access_log_mode": 0,
|
||||
"ignore_missing_services": false,
|
||||
"controller_type": "Handheld",
|
||||
"gui_columns": [ true, true, true, true, true, true, true, true, true ],
|
||||
"game_dirs": [],
|
||||
"gui_columns": {
|
||||
"fav_column": true,
|
||||
"icon_column": true,
|
||||
"app_column": true,
|
||||
"dev_column": true,
|
||||
"version_column": true,
|
||||
"time_played_column": true,
|
||||
"last_played_column": true,
|
||||
"file_ext_column": true,
|
||||
"file_size_column": true,
|
||||
"path_column": true
|
||||
},
|
||||
"game_dirs": [
|
||||
|
||||
],
|
||||
"enable_custom_theme": false,
|
||||
"custom_theme_path": "",
|
||||
"enable_keyboard": false,
|
||||
"keyboard_controls": {
|
||||
"left_joycon": {
|
||||
"stick_up": "W",
|
||||
@ -54,7 +71,7 @@
|
||||
"toggle_vsync": "Tab"
|
||||
}
|
||||
},
|
||||
"joystick_controls": {
|
||||
"joystick_controls": {
|
||||
"enabled": true,
|
||||
"index": 0,
|
||||
"deadzone": 0.05,
|
||||
@ -82,4 +99,4 @@
|
||||
"button_zr": "Axis5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,8 +7,8 @@ using Ryujinx.HLE;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using Ryujinx.HLE.HOS.Services;
|
||||
using Ryujinx.HLE.Input;
|
||||
using Ryujinx.UI;
|
||||
using Ryujinx.UI.Input;
|
||||
using Ryujinx.Ui;
|
||||
using Ryujinx.Ui.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@ -124,7 +124,7 @@ namespace Ryujinx
|
||||
/// <summary>
|
||||
/// Used to toggle columns in the GUI
|
||||
/// </summary>
|
||||
public List<bool> GuiColumns { get; set; }
|
||||
public GuiColumns GuiColumns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of directories containing games to be used to load games into the games list
|
||||
@ -154,7 +154,7 @@ namespace Ryujinx
|
||||
/// <summary>
|
||||
/// Controller control bindings
|
||||
/// </summary>
|
||||
public UI.Input.NpadController JoystickControls { get; private set; }
|
||||
public Ui.Input.NpadController JoystickControls { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads a configuration file from disk
|
||||
|
@ -1,7 +1,7 @@
|
||||
using Gtk;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Profiler;
|
||||
using Ryujinx.UI;
|
||||
using Ryujinx.Ui;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
@ -18,16 +18,20 @@ namespace Ryujinx
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
|
||||
GLib.ExceptionManager.UnhandledException += Glib_UnhandledException;
|
||||
|
||||
Profile.Initialize();
|
||||
|
||||
Application.Init();
|
||||
|
||||
Application gtkApplication = new Application("Ryujinx.Ryujinx", GLib.ApplicationFlags.None);
|
||||
MainWindow mainWindow = new MainWindow(args, gtkApplication);
|
||||
string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFs", "system", "prod.keys");
|
||||
string userProfilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".switch", "prod.keys");
|
||||
if (!File.Exists(appDataPath) && !File.Exists(userProfilePath))
|
||||
{
|
||||
GtkDialog.CreateErrorDialog($"Key file was not found. Please refer to `KEYS.md` for more info");
|
||||
}
|
||||
|
||||
gtkApplication.Register(GLib.Cancellable.Current);
|
||||
gtkApplication.AddWindow(mainWindow);
|
||||
MainWindow mainWindow = new MainWindow();
|
||||
mainWindow.Show();
|
||||
|
||||
if (args.Length == 1)
|
||||
@ -45,7 +49,7 @@ namespace Ryujinx
|
||||
|
||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var exception = e.ExceptionObject as Exception;
|
||||
Exception exception = e.ExceptionObject as Exception;
|
||||
|
||||
Logger.PrintError(LogClass.Emulation, $"Unhandled exception caught: {exception}");
|
||||
|
||||
@ -54,5 +58,17 @@ namespace Ryujinx
|
||||
Logger.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Glib_UnhandledException(GLib.UnhandledExceptionArgs e)
|
||||
{
|
||||
Exception exception = e.ExceptionObject as Exception;
|
||||
|
||||
Logger.PrintError(LogClass.Application, $"Unhandled exception caught: {exception}");
|
||||
|
||||
if (e.IsTerminating)
|
||||
{
|
||||
Logger.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
010034e005c9c000
|
||||
01004f8006a78000
|
||||
010051f00ac5e000
|
||||
010056e00853a000
|
||||
0100574009f9e000
|
||||
0100628004bce000
|
||||
0100633007d48000
|
||||
@ -16,15 +17,20 @@
|
||||
010068f00aa78000
|
||||
01006a800016e000
|
||||
010072800cbe8000
|
||||
01007300020fa000
|
||||
01007330027ee000
|
||||
0100749009844000
|
||||
01007a4008486000
|
||||
01007ef00011e000
|
||||
010080b00ad66000
|
||||
01008db008c2c000
|
||||
010094e00b52e000
|
||||
01009aa000faa000
|
||||
01009b90006dc000
|
||||
01009cc00c97c000
|
||||
0100a4200a284000
|
||||
0100a5c00d162000
|
||||
0100abf008968000
|
||||
0100ae000aebc000
|
||||
0100b3f000be2000
|
||||
0100bc2004ff4000
|
||||
|
@ -18,23 +18,50 @@
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Due to GtkSharp. -->
|
||||
<!-- Due to .net core 3.0 embedded resource loading -->
|
||||
<PropertyGroup>
|
||||
<EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64'">
|
||||
<DefineConstants>MACOS_BUILD</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Ui\AboutWindow.glade" />
|
||||
<None Remove="Ui\assets\BlueCon.png" />
|
||||
<None Remove="Ui\assets\ProCon.png" />
|
||||
<None Remove="Ui\assets\RedCon.png" />
|
||||
<None Remove="Ui\assets\NCAIcon.png" />
|
||||
<None Remove="Ui\assets\NROIcon.png" />
|
||||
<None Remove="Ui\assets\NSOIcon.png" />
|
||||
<None Remove="Ui\assets\NSPIcon.png" />
|
||||
<None Remove="Ui\assets\XCIIcon.png" />
|
||||
<None Remove="Ui\assets\DiscordLogo.png" />
|
||||
<None Remove="Ui\assets\GitHubLogo.png" />
|
||||
<None Remove="Ui\assets\JoyCon.png" />
|
||||
<None Remove="Ui\assets\PatreonLogo.png" />
|
||||
<None Remove="Ui\assets\Icon.png" />
|
||||
<None Remove="Ui\assets\TwitterLogo.png" />
|
||||
<None Remove="Ui\MainWindow.glade" />
|
||||
<None Remove="Ui\SwitchSettings.glade" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Ui\AboutWindow.glade" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxNCAIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxNROIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxNSOIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxNSPIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxXCIIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\BlueCon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ProCon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\RedCon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\NCAIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\NROIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\NSOIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\NSPIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\XCIIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\DiscordLogo.png" />
|
||||
<EmbeddedResource Include="Ui\assets\GitHubLogo.png" />
|
||||
<EmbeddedResource Include="Ui\assets\JoyCon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\PatreonLogo.png" />
|
||||
<EmbeddedResource Include="Ui\assets\ryujinxIcon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\Icon.png" />
|
||||
<EmbeddedResource Include="Ui\assets\TwitterLogo.png" />
|
||||
<EmbeddedResource Include="Ui\MainWindow.glade" />
|
||||
<EmbeddedResource Include="Ui\SwitchSettings.glade" />
|
||||
@ -42,8 +69,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.121" />
|
||||
<PackageReference Include="GtkSharp" Version="3.22.24.37" />
|
||||
<PackageReference Include="GtkSharp.Dependencies" Version="1.0.1" />
|
||||
<PackageReference Include="GtkSharp" Version="3.22.25.24" />
|
||||
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||
<PackageReference Include="JsonPrettyPrinter" Version="1.0.1.1" />
|
||||
<PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
|
||||
</ItemGroup>
|
||||
@ -61,9 +88,6 @@
|
||||
<None Update="Config.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Theme.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="RPsupported.dat">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
4054
Ryujinx/Theme.css
9
Ryujinx/Ui/AboutInfo.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
internal struct AboutInfo
|
||||
{
|
||||
public string InstallVersion;
|
||||
public string InstallCommit;
|
||||
public string InstallBranch;
|
||||
}
|
||||
}
|
@ -1,27 +1,22 @@
|
||||
using Gtk;
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Utf8Json;
|
||||
using Utf8Json.Resolvers;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.UI
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public struct Info
|
||||
{
|
||||
public string InstallVersion;
|
||||
public string InstallCommit;
|
||||
public string InstallBranch;
|
||||
}
|
||||
|
||||
public class AboutWindow : Window
|
||||
{
|
||||
public static Info Information { get; private set; }
|
||||
private static AboutInfo AboutInformation { get; set; }
|
||||
|
||||
#pragma warning disable 649
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable IDE0044
|
||||
[GUI] Window _aboutWin;
|
||||
[GUI] Label _versionText;
|
||||
[GUI] Image _ryujinxLogo;
|
||||
@ -29,7 +24,8 @@ namespace Ryujinx.UI
|
||||
[GUI] Image _gitHubLogo;
|
||||
[GUI] Image _discordLogo;
|
||||
[GUI] Image _twitterLogo;
|
||||
#pragma warning restore 649
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore IDE0044
|
||||
|
||||
public AboutWindow() : this(new Builder("Ryujinx.Ui.AboutWindow.glade")) { }
|
||||
|
||||
@ -37,8 +33,8 @@ namespace Ryujinx.UI
|
||||
{
|
||||
builder.Autoconnect(this);
|
||||
|
||||
_aboutWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png");
|
||||
_ryujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png", 100, 100);
|
||||
_aboutWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
|
||||
_ryujinxLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png" , 100, 100);
|
||||
_patreonLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.PatreonLogo.png", 30 , 30 );
|
||||
_gitHubLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.GitHubLogo.png" , 30 , 30 );
|
||||
_discordLogo.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.DiscordLogo.png", 30 , 30 );
|
||||
@ -50,10 +46,10 @@ namespace Ryujinx.UI
|
||||
|
||||
using (Stream stream = File.OpenRead(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "Installer", "Config", "Config.json")))
|
||||
{
|
||||
Information = JsonSerializer.Deserialize<Info>(stream, resolver);
|
||||
AboutInformation = JsonSerializer.Deserialize<AboutInfo>(stream, resolver);
|
||||
}
|
||||
|
||||
_versionText.Text = $"Version {Information.InstallVersion} - {Information.InstallBranch} ({Information.InstallCommit})";
|
||||
_versionText.Text = $"Version {AboutInformation.InstallVersion} - {AboutInformation.InstallBranch} ({AboutInformation.InstallCommit})";
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -61,7 +57,7 @@ namespace Ryujinx.UI
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenUrl(string url)
|
||||
private static void OpenUrl(string url)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
@ -78,39 +74,39 @@ namespace Ryujinx.UI
|
||||
}
|
||||
|
||||
//Events
|
||||
private void RyujinxButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://ryujinx.org");
|
||||
}
|
||||
|
||||
private void PatreonButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://www.patreon.com/ryujinx");
|
||||
}
|
||||
|
||||
private void GitHubButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://github.com/Ryujinx/Ryujinx");
|
||||
}
|
||||
|
||||
private void DiscordButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://discordapp.com/invite/N2FmfVc");
|
||||
}
|
||||
|
||||
private void TwitterButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://twitter.com/RyujinxEmu");
|
||||
}
|
||||
|
||||
private void ContributersButton_Pressed(object obj, ButtonPressEventArgs args)
|
||||
private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
|
||||
}
|
||||
|
||||
private void CloseToggle_Activated(object obj, EventArgs args)
|
||||
private void CloseToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
Destroy();
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,10 +154,10 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<object class="GtkLabel" id="license">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Unlicenced</property>
|
||||
<property name="label" translatable="yes">MIT License</property>
|
||||
<property name="justify">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
@ -168,7 +168,7 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<object class="GtkLabel" id="disclaimer">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Ryujinx is not affiliated with Nintendo,
|
||||
@ -523,11 +523,11 @@ Andy A (BaronKiko)</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEventBox" id="ContributersButton">
|
||||
<object class="GtkEventBox" id="ContributorsButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="valign">start</property>
|
||||
<signal name="button-press-event" handler="ContributersButton_Pressed" swapped="no"/>
|
||||
<signal name="button-press-event" handler="ContributorsButton_Pressed" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
|
11
Ryujinx/Ui/ApplicationAddedEventArgs.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class ApplicationAddedEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationData AppData { get; set; }
|
||||
public int NumAppsFound { get; set; }
|
||||
public int NumAppsLoaded { get; set; }
|
||||
}
|
||||
}
|
17
Ryujinx/Ui/ApplicationData.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public struct ApplicationData
|
||||
{
|
||||
public bool Favorite { get; set; }
|
||||
public byte[] Icon { get; set; }
|
||||
public string TitleName { get; set; }
|
||||
public string TitleId { get; set; }
|
||||
public string Developer { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string TimePlayed { get; set; }
|
||||
public string LastPlayed { get; set; }
|
||||
public string FileExtension { get; set; }
|
||||
public string FileSize { get; set; }
|
||||
public string Path { get; set; }
|
||||
}
|
||||
}
|
@ -1,66 +1,50 @@
|
||||
using LibHac;
|
||||
using JsonPrettyPrinterPlus;
|
||||
using LibHac;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.FsSystem.NcaUtils;
|
||||
using LibHac.Spl;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Npdm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Utf8Json;
|
||||
using Utf8Json.Resolvers;
|
||||
|
||||
using SystemState = Ryujinx.HLE.HOS.SystemState;
|
||||
using TitleLanguage = Ryujinx.HLE.HOS.SystemState.TitleLanguage;
|
||||
|
||||
namespace Ryujinx.UI
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class ApplicationLibrary
|
||||
{
|
||||
private static Keyset KeySet;
|
||||
private static SystemState.TitleLanguage DesiredTitleLanguage;
|
||||
public static event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
||||
|
||||
private const double SecondsPerMinute = 60.0;
|
||||
private const double SecondsPerHour = SecondsPerMinute * 60;
|
||||
private const double SecondsPerDay = SecondsPerHour * 24;
|
||||
private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png");
|
||||
private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png");
|
||||
private static readonly byte[] _ncaIcon = GetResourceBytes("Ryujinx.Ui.assets.NCAIcon.png");
|
||||
private static readonly byte[] _nroIcon = GetResourceBytes("Ryujinx.Ui.assets.NROIcon.png");
|
||||
private static readonly byte[] _nsoIcon = GetResourceBytes("Ryujinx.Ui.assets.NSOIcon.png");
|
||||
|
||||
public static byte[] RyujinxNspIcon { get; private set; }
|
||||
public static byte[] RyujinxXciIcon { get; private set; }
|
||||
public static byte[] RyujinxNcaIcon { get; private set; }
|
||||
public static byte[] RyujinxNroIcon { get; private set; }
|
||||
public static byte[] RyujinxNsoIcon { get; private set; }
|
||||
private static Keyset _keySet;
|
||||
private static TitleLanguage _desiredTitleLanguage;
|
||||
private static ApplicationMetadata _appMetadata;
|
||||
|
||||
public static List<ApplicationData> ApplicationLibraryData { get; private set; }
|
||||
|
||||
public struct ApplicationData
|
||||
public static void LoadApplications(List<string> appDirs, Keyset keySet, TitleLanguage desiredTitleLanguage)
|
||||
{
|
||||
public byte[] Icon;
|
||||
public string TitleName;
|
||||
public string TitleId;
|
||||
public string Developer;
|
||||
public string Version;
|
||||
public string TimePlayed;
|
||||
public string LastPlayed;
|
||||
public string FileExt;
|
||||
public string FileSize;
|
||||
public string Path;
|
||||
}
|
||||
int numApplicationsFound = 0;
|
||||
int numApplicationsLoaded = 0;
|
||||
|
||||
public static void Init(List<string> AppDirs, Keyset keySet, SystemState.TitleLanguage desiredTitleLanguage)
|
||||
{
|
||||
KeySet = keySet;
|
||||
DesiredTitleLanguage = desiredTitleLanguage;
|
||||
|
||||
// Loads the default application Icons
|
||||
RyujinxNspIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNSPIcon.png");
|
||||
RyujinxXciIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxXCIIcon.png");
|
||||
RyujinxNcaIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNCAIcon.png");
|
||||
RyujinxNroIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNROIcon.png");
|
||||
RyujinxNsoIcon = GetResourceBytes("Ryujinx.Ui.assets.ryujinxNSOIcon.png");
|
||||
_keySet = keySet;
|
||||
_desiredTitleLanguage = desiredTitleLanguage;
|
||||
|
||||
// Builds the applications list with paths to found applications
|
||||
List<string> applications = new List<string>();
|
||||
foreach (string appDir in AppDirs)
|
||||
foreach (string appDir in appDirs)
|
||||
{
|
||||
if (Directory.Exists(appDir) == false)
|
||||
{
|
||||
@ -69,30 +53,80 @@ namespace Ryujinx.UI
|
||||
continue;
|
||||
}
|
||||
|
||||
DirectoryInfo AppDirInfo = new DirectoryInfo(appDir);
|
||||
foreach (FileInfo App in AppDirInfo.GetFiles())
|
||||
foreach (string app in Directory.GetFiles(appDir, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if ((Path.GetExtension(App.ToString()) == ".xci") ||
|
||||
(Path.GetExtension(App.ToString()) == ".nca") ||
|
||||
(Path.GetExtension(App.ToString()) == ".nsp") ||
|
||||
(Path.GetExtension(App.ToString()) == ".pfs0") ||
|
||||
(Path.GetExtension(App.ToString()) == ".nro") ||
|
||||
(Path.GetExtension(App.ToString()) == ".nso"))
|
||||
if ((Path.GetExtension(app) == ".xci") ||
|
||||
(Path.GetExtension(app) == ".nro") ||
|
||||
(Path.GetExtension(app) == ".nso") ||
|
||||
(Path.GetFileName(app) == "hbl.nsp"))
|
||||
{
|
||||
applications.Add(App.ToString());
|
||||
applications.Add(app);
|
||||
numApplicationsFound++;
|
||||
}
|
||||
else if ((Path.GetExtension(app) == ".nsp") || (Path.GetExtension(app) == ".pfs0"))
|
||||
{
|
||||
try
|
||||
{
|
||||
bool hasMainNca = false;
|
||||
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage());
|
||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(_keySet, ncaFile.AsStorage());
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program && !nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
hasMainNca = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMainNca)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed.");
|
||||
}
|
||||
|
||||
applications.Add(app);
|
||||
numApplicationsFound++;
|
||||
}
|
||||
else if (Path.GetExtension(app) == ".nca")
|
||||
{
|
||||
try
|
||||
{
|
||||
Nca nca = new Nca(_keySet, new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage());
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.Header.ContentType != NcaContentType.Program || nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed.");
|
||||
}
|
||||
|
||||
applications.Add(app);
|
||||
numApplicationsFound++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loops through applications list, creating a struct for each application and then adding the struct to a list of structs
|
||||
ApplicationLibraryData = new List<ApplicationData>();
|
||||
// Loops through applications list, creating a struct and then firing an event containing the struct for each application
|
||||
foreach (string applicationPath in applications)
|
||||
{
|
||||
double filesize = new FileInfo(applicationPath).Length * 0.000000000931;
|
||||
string titleName = null;
|
||||
string titleId = null;
|
||||
string developer = null;
|
||||
string version = null;
|
||||
double fileSize = new FileInfo(applicationPath).Length * 0.000000000931;
|
||||
string titleName = "Unknown";
|
||||
string titleId = "0000000000000000";
|
||||
string developer = "Unknown";
|
||||
string version = "0";
|
||||
byte[] applicationIcon = null;
|
||||
|
||||
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
|
||||
@ -103,158 +137,48 @@ namespace Ryujinx.UI
|
||||
{
|
||||
try
|
||||
{
|
||||
IFileSystem controlFs = null;
|
||||
|
||||
// Store the ControlFS in variable called controlFs
|
||||
PartitionFileSystem pfs;
|
||||
|
||||
if (Path.GetExtension(applicationPath) == ".xci")
|
||||
{
|
||||
Xci xci = new Xci(KeySet, file.AsStorage());
|
||||
Xci xci = new Xci(_keySet, file.AsStorage());
|
||||
|
||||
controlFs = GetControlFs(xci.OpenPartition(XciPartitionType.Secure));
|
||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
||||
}
|
||||
else
|
||||
{
|
||||
controlFs = GetControlFs(new PartitionFileSystem(file.AsStorage()));
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
}
|
||||
|
||||
// Creates NACP class from the NACP file
|
||||
controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp", OpenMode.Read).ThrowIfFailure();
|
||||
// Store the ControlFS in variable called controlFs
|
||||
IFileSystem controlFs = GetControlFs(pfs);
|
||||
|
||||
Nacp controlData = new Nacp(controlNacpFile.AsStream());
|
||||
|
||||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = controlData.DisplayVersion;
|
||||
|
||||
titleName = controlData.Descriptions[(int)DesiredTitleLanguage].Title;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
// If this is null then this is probably not a normal NSP, it's probably an ExeFS as an NSP
|
||||
if (controlFs == null)
|
||||
{
|
||||
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
}
|
||||
applicationIcon = _nspIcon;
|
||||
|
||||
titleId = controlData.PresenceGroupId.ToString("x16");
|
||||
Result result = pfs.OpenFile(out IFile npdmFile, "/main.npdm", OpenMode.Read);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = controlData.SaveDataOwnerId.ToString("x16");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||
}
|
||||
|
||||
developer = controlData.Descriptions[(int)DesiredTitleLanguage].Developer;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(developer))
|
||||
{
|
||||
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
||||
}
|
||||
|
||||
// Read the icon from the ControlFS and store it as a byte array
|
||||
try
|
||||
{
|
||||
controlFs.OpenFile(out IFile icon, $"/icon_{DesiredTitleLanguage}.dat", OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
if (result != ResultFs.PathNotFound)
|
||||
{
|
||||
icon.AsStream().CopyTo(stream);
|
||||
applicationIcon = stream.ToArray();
|
||||
Npdm npdm = new Npdm(npdmFile.AsStream());
|
||||
|
||||
titleName = npdm.TitleName;
|
||||
titleId = npdm.Aci0.TitleId.ToString("x16");
|
||||
}
|
||||
}
|
||||
catch (HorizonResultException)
|
||||
else
|
||||
{
|
||||
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
||||
{
|
||||
if (entry.Name == "control.nacp")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Creates NACP class from the NACP file
|
||||
controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp", OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
controlFs.OpenFile(out IFile icon, entry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
icon.AsStream().CopyTo(stream);
|
||||
applicationIcon = stream.ToArray();
|
||||
}
|
||||
|
||||
if (applicationIcon != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (applicationIcon == null)
|
||||
{
|
||||
applicationIcon = NspOrXciIcon(applicationPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MissingKeyException exception)
|
||||
{
|
||||
titleName = "Unknown";
|
||||
titleId = "Unknown";
|
||||
developer = "Unknown";
|
||||
version = "?";
|
||||
applicationIcon = NspOrXciIcon(applicationPath);
|
||||
|
||||
Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
titleName = "Unknown";
|
||||
titleId = "Unknown";
|
||||
developer = "Unknown";
|
||||
version = "?";
|
||||
applicationIcon = NspOrXciIcon(applicationPath);
|
||||
|
||||
Logger.PrintWarning(LogClass.Application, $"The file is not an NCA file or the header key is incorrect. Errored File: {applicationPath}");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"This warning usualy means that you have a DLC in one of you game directories\n{exception}");
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (Path.GetExtension(applicationPath) == ".nro")
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(file);
|
||||
|
||||
byte[] Read(long Position, int Size)
|
||||
{
|
||||
file.Seek(Position, SeekOrigin.Begin);
|
||||
|
||||
return reader.ReadBytes(Size);
|
||||
}
|
||||
|
||||
file.Seek(24, SeekOrigin.Begin);
|
||||
int AssetOffset = reader.ReadInt32();
|
||||
|
||||
if (Encoding.ASCII.GetString(Read(AssetOffset, 4)) == "ASET")
|
||||
{
|
||||
byte[] IconSectionInfo = Read(AssetOffset + 8, 0x10);
|
||||
|
||||
long iconOffset = BitConverter.ToInt64(IconSectionInfo, 0);
|
||||
long iconSize = BitConverter.ToInt64(IconSectionInfo, 8);
|
||||
|
||||
ulong nacpOffset = reader.ReadUInt64();
|
||||
ulong nacpSize = reader.ReadUInt64();
|
||||
|
||||
// Reads and stores game icon as byte array
|
||||
applicationIcon = Read(AssetOffset + iconOffset, (int)iconSize);
|
||||
|
||||
// Creates memory stream out of byte array which is the NACP
|
||||
using (MemoryStream stream = new MemoryStream(Read(AssetOffset + (int)nacpOffset, (int)nacpSize)))
|
||||
{
|
||||
// Creates NACP class from the memory stream
|
||||
Nacp controlData = new Nacp(stream);
|
||||
Nacp controlData = new Nacp(controlNacpFile.AsStream());
|
||||
|
||||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = controlData.DisplayVersion;
|
||||
|
||||
titleName = controlData.Descriptions[(int)DesiredTitleLanguage].Title;
|
||||
titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
@ -273,7 +197,123 @@ namespace Ryujinx.UI
|
||||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||
}
|
||||
|
||||
developer = controlData.Descriptions[(int)DesiredTitleLanguage].Developer;
|
||||
developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(developer))
|
||||
{
|
||||
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
||||
}
|
||||
|
||||
// Read the icon from the ControlFS and store it as a byte array
|
||||
try
|
||||
{
|
||||
controlFs.OpenFile(out IFile icon, $"/icon_{_desiredTitleLanguage}.dat", OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
icon.AsStream().CopyTo(stream);
|
||||
applicationIcon = stream.ToArray();
|
||||
}
|
||||
}
|
||||
catch (HorizonResultException)
|
||||
{
|
||||
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
||||
{
|
||||
if (entry.Name == "control.nacp")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
controlFs.OpenFile(out IFile icon, entry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
icon.AsStream().CopyTo(stream);
|
||||
applicationIcon = stream.ToArray();
|
||||
}
|
||||
|
||||
if (applicationIcon != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (applicationIcon == null)
|
||||
{
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MissingKeyException exception)
|
||||
{
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
||||
|
||||
Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
||||
|
||||
Logger.PrintWarning(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
|
||||
}
|
||||
}
|
||||
else if (Path.GetExtension(applicationPath) == ".nro")
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(file);
|
||||
|
||||
byte[] Read(long position, int size)
|
||||
{
|
||||
file.Seek(position, SeekOrigin.Begin);
|
||||
|
||||
return reader.ReadBytes(size);
|
||||
}
|
||||
|
||||
file.Seek(24, SeekOrigin.Begin);
|
||||
int assetOffset = reader.ReadInt32();
|
||||
|
||||
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
|
||||
{
|
||||
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
|
||||
|
||||
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
|
||||
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
||||
|
||||
ulong nacpOffset = reader.ReadUInt64();
|
||||
ulong nacpSize = reader.ReadUInt64();
|
||||
|
||||
// Reads and stores game icon as byte array
|
||||
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
||||
|
||||
// Creates memory stream out of byte array which is the NACP
|
||||
using (MemoryStream stream = new MemoryStream(Read(assetOffset + (int)nacpOffset, (int)nacpSize)))
|
||||
{
|
||||
// Creates NACP class from the memory stream
|
||||
Nacp controlData = new Nacp(stream);
|
||||
|
||||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = controlData.DisplayVersion;
|
||||
|
||||
titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
}
|
||||
|
||||
titleId = controlData.PresenceGroupId.ToString("x16");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = controlData.SaveDataOwnerId.ToString("x16");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||
}
|
||||
|
||||
developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(developer))
|
||||
{
|
||||
@ -283,59 +323,50 @@ namespace Ryujinx.UI
|
||||
}
|
||||
else
|
||||
{
|
||||
applicationIcon = RyujinxNroIcon;
|
||||
titleName = "Application";
|
||||
titleId = "0000000000000000";
|
||||
developer = "Unknown";
|
||||
version = "?";
|
||||
applicationIcon = _nroIcon;
|
||||
}
|
||||
}
|
||||
// If its an NCA or NSO we just set defaults
|
||||
else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso"))
|
||||
{
|
||||
if (Path.GetExtension(applicationPath) == ".nca")
|
||||
{
|
||||
applicationIcon = RyujinxNcaIcon;
|
||||
}
|
||||
else if (Path.GetExtension(applicationPath) == ".nso")
|
||||
{
|
||||
applicationIcon = RyujinxNsoIcon;
|
||||
}
|
||||
|
||||
string fileName = Path.GetFileName(applicationPath);
|
||||
string fileExt = Path.GetExtension(applicationPath);
|
||||
|
||||
StringBuilder titlename = new StringBuilder();
|
||||
titlename.Append(fileName);
|
||||
titlename.Remove(fileName.Length - fileExt.Length, fileExt.Length);
|
||||
|
||||
titleName = titlename.ToString();
|
||||
titleId = "0000000000000000";
|
||||
version = "?";
|
||||
developer = "Unknown";
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".nca" ? _ncaIcon : _nsoIcon;
|
||||
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||
}
|
||||
}
|
||||
|
||||
string[] playedData = GetPlayedData(titleId, "00000000000000000000000000000001");
|
||||
(bool favorite, string timePlayed, string lastPlayed) = GetMetadata(titleId);
|
||||
|
||||
ApplicationData data = new ApplicationData()
|
||||
{
|
||||
Icon = applicationIcon,
|
||||
TitleName = titleName,
|
||||
TitleId = titleId,
|
||||
Developer = developer,
|
||||
Version = version,
|
||||
TimePlayed = playedData[0],
|
||||
LastPlayed = playedData[1],
|
||||
FileExt = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1),
|
||||
FileSize = (filesize < 1) ? (filesize * 1024).ToString("0.##") + "MB" : filesize.ToString("0.##") + "GB",
|
||||
Path = applicationPath,
|
||||
Favorite = favorite,
|
||||
Icon = applicationIcon,
|
||||
TitleName = titleName,
|
||||
TitleId = titleId,
|
||||
Developer = developer,
|
||||
Version = version,
|
||||
TimePlayed = timePlayed,
|
||||
LastPlayed = lastPlayed,
|
||||
FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1),
|
||||
FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + "MB" : fileSize.ToString("0.##") + "GB",
|
||||
Path = applicationPath,
|
||||
};
|
||||
|
||||
ApplicationLibraryData.Add(data);
|
||||
numApplicationsLoaded++;
|
||||
|
||||
OnApplicationAdded(new ApplicationAddedEventArgs()
|
||||
{
|
||||
AppData = data,
|
||||
NumAppsFound = numApplicationsFound,
|
||||
NumAppsLoaded = numApplicationsLoaded
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected static void OnApplicationAdded(ApplicationAddedEventArgs e)
|
||||
{
|
||||
ApplicationAdded?.Invoke(null, e);
|
||||
}
|
||||
|
||||
private static byte[] GetResourceBytes(string resourceName)
|
||||
{
|
||||
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
|
||||
@ -346,29 +377,29 @@ namespace Ryujinx.UI
|
||||
return resourceByteArray;
|
||||
}
|
||||
|
||||
private static IFileSystem GetControlFs(PartitionFileSystem Pfs)
|
||||
private static IFileSystem GetControlFs(PartitionFileSystem pfs)
|
||||
{
|
||||
Nca controlNca = null;
|
||||
|
||||
// Add keys to keyset if needed
|
||||
foreach (DirectoryEntryEx ticketEntry in Pfs.EnumerateEntries("/", "*.tik"))
|
||||
// Add keys to key set if needed
|
||||
foreach (DirectoryEntryEx ticketEntry in pfs.EnumerateEntries("/", "*.tik"))
|
||||
{
|
||||
Result result = Pfs.OpenFile(out IFile ticketFile, ticketEntry.FullPath, OpenMode.Read);
|
||||
Result result = pfs.OpenFile(out IFile ticketFile, ticketEntry.FullPath, OpenMode.Read);
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
||||
|
||||
KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
|
||||
_keySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_keySet)));
|
||||
}
|
||||
}
|
||||
|
||||
// Find the Control NCA and store it in variable called controlNca
|
||||
foreach (DirectoryEntryEx fileEntry in Pfs.EnumerateEntries("/", "*.nca"))
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
Pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(KeySet, ncaFile.AsStorage());
|
||||
Nca nca = new Nca(_keySet, ncaFile.AsStorage());
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Control)
|
||||
{
|
||||
@ -377,84 +408,65 @@ namespace Ryujinx.UI
|
||||
}
|
||||
|
||||
// Return the ControlFS
|
||||
return controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
|
||||
return controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
|
||||
}
|
||||
|
||||
private static string[] GetPlayedData(string TitleId, string UserId)
|
||||
private static (bool favorite, string timePlayed, string lastPlayed) GetMetadata(string titleId)
|
||||
{
|
||||
try
|
||||
string metadataFolder = Path.Combine(new VirtualFileSystem().GetBasePath(), "games", titleId, "gui");
|
||||
string metadataFile = Path.Combine(metadataFolder, "metadata.json");
|
||||
|
||||
IJsonFormatterResolver resolver = CompositeResolver.Create(StandardResolver.AllowPrivateSnakeCase);
|
||||
|
||||
if (!File.Exists(metadataFile))
|
||||
{
|
||||
string[] playedData = new string[2];
|
||||
string savePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", UserId, TitleId);
|
||||
Directory.CreateDirectory(metadataFolder);
|
||||
|
||||
if (File.Exists(Path.Combine(savePath, "TimePlayed.dat")) == false)
|
||||
_appMetadata = new ApplicationMetadata
|
||||
{
|
||||
Directory.CreateDirectory(savePath);
|
||||
using (FileStream file = File.OpenWrite(Path.Combine(savePath, "TimePlayed.dat")))
|
||||
{
|
||||
file.Write(Encoding.ASCII.GetBytes("0"));
|
||||
}
|
||||
}
|
||||
using (FileStream fs = File.OpenRead(Path.Combine(savePath, "TimePlayed.dat")))
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(fs))
|
||||
{
|
||||
float timePlayed = float.Parse(sr.ReadLine());
|
||||
Favorite = false,
|
||||
TimePlayed = 0,
|
||||
LastPlayed = "Never"
|
||||
};
|
||||
|
||||
if (timePlayed < SecondsPerMinute)
|
||||
{
|
||||
playedData[0] = $"{timePlayed}s";
|
||||
}
|
||||
else if (timePlayed < SecondsPerHour)
|
||||
{
|
||||
playedData[0] = $"{Math.Round(timePlayed / SecondsPerMinute, 2, MidpointRounding.AwayFromZero)} mins";
|
||||
}
|
||||
else if (timePlayed < SecondsPerDay)
|
||||
{
|
||||
playedData[0] = $"{Math.Round(timePlayed / SecondsPerHour , 2, MidpointRounding.AwayFromZero)} hrs";
|
||||
}
|
||||
else
|
||||
{
|
||||
playedData[0] = $"{Math.Round(timePlayed / SecondsPerDay , 2, MidpointRounding.AwayFromZero)} days";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(savePath, "LastPlayed.dat")) == false)
|
||||
{
|
||||
Directory.CreateDirectory(savePath);
|
||||
using (FileStream file = File.OpenWrite(Path.Combine(savePath, "LastPlayed.dat")))
|
||||
{
|
||||
file.Write(Encoding.ASCII.GetBytes("Never"));
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream fs = File.OpenRead(Path.Combine(savePath, "LastPlayed.dat")))
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(fs))
|
||||
{
|
||||
playedData[1] = sr.ReadLine();
|
||||
}
|
||||
}
|
||||
|
||||
return playedData;
|
||||
byte[] saveData = JsonSerializer.Serialize(_appMetadata, resolver);
|
||||
File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
|
||||
}
|
||||
catch
|
||||
|
||||
using (Stream stream = File.OpenRead(metadataFile))
|
||||
{
|
||||
return new string[] { "Unknown", "Unknown" };
|
||||
_appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
|
||||
}
|
||||
|
||||
return (_appMetadata.Favorite, ConvertSecondsToReadableString(_appMetadata.TimePlayed), _appMetadata.LastPlayed);
|
||||
}
|
||||
|
||||
private static byte[] NspOrXciIcon(string applicationPath)
|
||||
private static string ConvertSecondsToReadableString(double seconds)
|
||||
{
|
||||
if (Path.GetExtension(applicationPath) == ".xci")
|
||||
const int secondsPerMinute = 60;
|
||||
const int secondsPerHour = secondsPerMinute * 60;
|
||||
const int secondsPerDay = secondsPerHour * 24;
|
||||
|
||||
string readableString;
|
||||
|
||||
if (seconds < secondsPerMinute)
|
||||
{
|
||||
return RyujinxXciIcon;
|
||||
readableString = $"{seconds}s";
|
||||
}
|
||||
else if (seconds < secondsPerHour)
|
||||
{
|
||||
readableString = $"{Math.Round(seconds / secondsPerMinute, 2, MidpointRounding.AwayFromZero)} mins";
|
||||
}
|
||||
else if (seconds < secondsPerDay)
|
||||
{
|
||||
readableString = $"{Math.Round(seconds / secondsPerHour, 2, MidpointRounding.AwayFromZero)} hrs";
|
||||
}
|
||||
else
|
||||
{
|
||||
return RyujinxNspIcon;
|
||||
readableString = $"{Math.Round(seconds / secondsPerDay, 2, MidpointRounding.AwayFromZero)} days";
|
||||
}
|
||||
|
||||
return readableString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
9
Ryujinx/Ui/ApplicationMetadata.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
internal struct ApplicationMetadata
|
||||
{
|
||||
public bool Favorite { get; set; }
|
||||
public double TimePlayed { get; set; }
|
||||
public string LastPlayed { get; set; }
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ using System.Threading;
|
||||
|
||||
using Stopwatch = System.Diagnostics.Stopwatch;
|
||||
|
||||
namespace Ryujinx.UI
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class GlScreen : GameWindow
|
||||
{
|
||||
@ -297,10 +297,13 @@ namespace Ryujinx.UI
|
||||
double hostFps = _device.Statistics.GetSystemFrameRate();
|
||||
double gameFps = _device.Statistics.GetGameFrameRate();
|
||||
|
||||
string titleSection = string.IsNullOrWhiteSpace(_device.System.CurrentTitle) ? string.Empty
|
||||
: " | " + _device.System.CurrentTitle;
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty
|
||||
: " | " + _device.System.TitleName;
|
||||
|
||||
_newTitle = $"Ryujinx{titleSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " +
|
||||
string titleIDSection = string.IsNullOrWhiteSpace(_device.System.TitleId) ? string.Empty
|
||||
: " | " + _device.System.TitleId.ToUpper();
|
||||
|
||||
_newTitle = $"Ryujinx{titleNameSection}{titleIDSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " +
|
||||
$"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}";
|
||||
|
||||
_titleEvent = true;
|
||||
|
23
Ryujinx/Ui/GtkDialog.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Gtk;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
internal class GtkDialog
|
||||
{
|
||||
internal static void CreateErrorDialog(string errorMessage)
|
||||
{
|
||||
MessageDialog errorDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, null)
|
||||
{
|
||||
Title = "Ryujinx - Error",
|
||||
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
|
||||
Text = "Ryujinx has encountered an error",
|
||||
SecondaryText = errorMessage,
|
||||
WindowPosition = WindowPosition.Center
|
||||
};
|
||||
errorDialog.SetSizeRequest(100, 20);
|
||||
errorDialog.Run();
|
||||
errorDialog.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
16
Ryujinx/Ui/GuiColumns.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public struct GuiColumns
|
||||
{
|
||||
public bool FavColumn;
|
||||
public bool IconColumn;
|
||||
public bool AppColumn;
|
||||
public bool DevColumn;
|
||||
public bool VersionColumn;
|
||||
public bool TimePlayedColumn;
|
||||
public bool LastPlayedColumn;
|
||||
public bool FileExtColumn;
|
||||
public bool FileSizeColumn;
|
||||
public bool PathColumn;
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
using DiscordRPC;
|
||||
using Gtk;
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
using JsonPrettyPrinterPlus;
|
||||
using Ryujinx.Audio;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gal;
|
||||
using Ryujinx.Graphics.Gal.OpenGL;
|
||||
using Ryujinx.Graphics.Gal;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Profiler;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
@ -12,25 +13,42 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Utf8Json;
|
||||
using Utf8Json.Resolvers;
|
||||
|
||||
namespace Ryujinx.UI
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class MainWindow : Window
|
||||
{
|
||||
internal static HLE.Switch _device;
|
||||
private static HLE.Switch _device;
|
||||
|
||||
private static IGalRenderer _renderer;
|
||||
|
||||
private static IAalOutput _audioOut;
|
||||
|
||||
private static Application _gtkApplication;
|
||||
private static GlScreen _screen;
|
||||
|
||||
private static ListStore _tableStore;
|
||||
|
||||
private static bool _gameLoaded = false;
|
||||
private static bool _updatingGameTable;
|
||||
private static bool _gameLoaded;
|
||||
private static bool _ending;
|
||||
|
||||
private static string _userId = "00000000000000000000000000000001";
|
||||
private static TreeViewColumn _favColumn;
|
||||
private static TreeViewColumn _appColumn;
|
||||
private static TreeViewColumn _devColumn;
|
||||
private static TreeViewColumn _versionColumn;
|
||||
private static TreeViewColumn _timePlayedColumn;
|
||||
private static TreeViewColumn _lastPlayedColumn;
|
||||
private static TreeViewColumn _fileExtColumn;
|
||||
private static TreeViewColumn _fileSizeColumn;
|
||||
private static TreeViewColumn _pathColumn;
|
||||
|
||||
private static TreeView _treeView;
|
||||
|
||||
public static bool DiscordIntegrationEnabled { get; set; }
|
||||
|
||||
@ -38,12 +56,14 @@ namespace Ryujinx.UI
|
||||
|
||||
public static RichPresence DiscordPresence;
|
||||
|
||||
#pragma warning disable 649
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable IDE0044
|
||||
[GUI] Window _mainWin;
|
||||
[GUI] CheckMenuItem _fullScreen;
|
||||
[GUI] MenuItem _stopEmulation;
|
||||
[GUI] CheckMenuItem _favToggle;
|
||||
[GUI] CheckMenuItem _iconToggle;
|
||||
[GUI] CheckMenuItem _titleToggle;
|
||||
[GUI] CheckMenuItem _appToggle;
|
||||
[GUI] CheckMenuItem _developerToggle;
|
||||
[GUI] CheckMenuItem _versionToggle;
|
||||
[GUI] CheckMenuItem _timePlayedToggle;
|
||||
@ -51,28 +71,33 @@ namespace Ryujinx.UI
|
||||
[GUI] CheckMenuItem _fileExtToggle;
|
||||
[GUI] CheckMenuItem _fileSizeToggle;
|
||||
[GUI] CheckMenuItem _pathToggle;
|
||||
[GUI] Box _box;
|
||||
[GUI] TreeView _gameTable;
|
||||
[GUI] GLArea _glScreen;
|
||||
#pragma warning restore 649
|
||||
[GUI] Label _progressLabel;
|
||||
[GUI] LevelBar _progressBar;
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore IDE0044
|
||||
|
||||
public MainWindow(string[] args, Application gtkApplication) : this(new Builder("Ryujinx.Ui.MainWindow.glade"), args, gtkApplication) { }
|
||||
public MainWindow() : this(new Builder("Ryujinx.Ui.MainWindow.glade")) { }
|
||||
|
||||
private MainWindow(Builder builder, string[] args, Application gtkApplication) : base(builder.GetObject("_mainWin").Handle)
|
||||
private MainWindow(Builder builder) : base(builder.GetObject("_mainWin").Handle)
|
||||
{
|
||||
builder.Autoconnect(this);
|
||||
|
||||
DeleteEvent += Window_Close;
|
||||
|
||||
ApplicationLibrary.ApplicationAdded += Application_Added;
|
||||
|
||||
_renderer = new OglRenderer();
|
||||
|
||||
_audioOut = InitializeAudioEngine();
|
||||
|
||||
_device = new HLE.Switch(_renderer, _audioOut);
|
||||
|
||||
_treeView = _gameTable;
|
||||
|
||||
Configuration.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
Configuration.InitialConfigure(_device);
|
||||
|
||||
ApplicationLibrary.Init(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage);
|
||||
|
||||
_gtkApplication = gtkApplication;
|
||||
|
||||
ApplyTheme();
|
||||
|
||||
if (DiscordIntegrationEnabled)
|
||||
@ -94,117 +119,130 @@ namespace Ryujinx.UI
|
||||
DiscordClient.SetPresence(DiscordPresence);
|
||||
}
|
||||
|
||||
builder.Autoconnect(this);
|
||||
|
||||
DeleteEvent += Window_Close;
|
||||
|
||||
_mainWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png");
|
||||
_mainWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
|
||||
_stopEmulation.Sensitive = false;
|
||||
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[0]) { _iconToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[1]) { _titleToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[2]) { _developerToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[3]) { _versionToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[4]) { _timePlayedToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[5]) { _lastPlayedToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[6]) { _fileExtToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[7]) { _fileSizeToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[8]) { _pathToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _favToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _iconToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _appToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _developerToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.VersionColumn) { _versionToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _timePlayedToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _lastPlayedToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _fileExtToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _fileSizeToggle.Active = true; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _pathToggle.Active = true; }
|
||||
|
||||
if (args.Length == 1)
|
||||
_gameTable.Model = _tableStore = new ListStore(
|
||||
typeof(bool),
|
||||
typeof(Gdk.Pixbuf),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string));
|
||||
|
||||
_tableStore.SetSortFunc(5, TimePlayedSort);
|
||||
_tableStore.SetSortFunc(6, LastPlayedSort);
|
||||
_tableStore.SetSortFunc(8, FileSizeSort);
|
||||
_tableStore.SetSortColumnId(0, SortType.Descending);
|
||||
|
||||
UpdateColumns();
|
||||
#pragma warning disable CS4014
|
||||
UpdateGameTable();
|
||||
#pragma warning restore CS4014
|
||||
}
|
||||
|
||||
internal static void ApplyTheme()
|
||||
{
|
||||
if (!SwitchSettings.SwitchConfig.EnableCustomTheme)
|
||||
{
|
||||
// Temporary code section start, remove this section when game is rendered to the GLArea in the GUI
|
||||
_box.Remove(_glScreen);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[0]) { _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 0); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[1]) { _gameTable.AppendColumn("Application", new CellRendererText(), "text", 1); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[2]) { _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 2); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[3]) { _gameTable.AppendColumn("Version", new CellRendererText(), "text", 3); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[4]) { _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 4); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[5]) { _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 5); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[6]) { _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 6); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[7]) { _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 7); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[8]) { _gameTable.AppendColumn("Path", new CellRendererText(), "text", 8); }
|
||||
if (File.Exists(SwitchSettings.SwitchConfig.CustomThemePath) && (System.IO.Path.GetExtension(SwitchSettings.SwitchConfig.CustomThemePath) == ".css"))
|
||||
{
|
||||
CssProvider cssProvider = new CssProvider();
|
||||
|
||||
_tableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
|
||||
_gameTable.Model = _tableStore;
|
||||
cssProvider.LoadFromPath(SwitchSettings.SwitchConfig.CustomThemePath);
|
||||
|
||||
UpdateGameTable();
|
||||
// Temporary code section end
|
||||
StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800);
|
||||
}
|
||||
else
|
||||
{
|
||||
_box.Remove(_glScreen);
|
||||
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[0]) { _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 0); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[1]) { _gameTable.AppendColumn("Application", new CellRendererText(), "text", 1); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[2]) { _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 2); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[3]) { _gameTable.AppendColumn("Version", new CellRendererText(), "text", 3); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[4]) { _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 4); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[5]) { _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 5); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[6]) { _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 6); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[7]) { _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 7); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns[8]) { _gameTable.AppendColumn("Path", new CellRendererText(), "text", 8); }
|
||||
|
||||
_tableStore = new ListStore(typeof(Gdk.Pixbuf), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string));
|
||||
_gameTable.Model = _tableStore;
|
||||
|
||||
UpdateGameTable();
|
||||
Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{SwitchSettings.SwitchConfig.CustomThemePath}\".");
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateErrorDialog(string errorMessage)
|
||||
private void UpdateColumns()
|
||||
{
|
||||
MessageDialog errorDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, errorMessage)
|
||||
foreach (TreeViewColumn column in _gameTable.Columns)
|
||||
{
|
||||
Title = "Ryujinx - Error",
|
||||
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png"),
|
||||
WindowPosition = WindowPosition.Center
|
||||
};
|
||||
errorDialog.SetSizeRequest(100, 20);
|
||||
errorDialog.Run();
|
||||
errorDialog.Destroy();
|
||||
_gameTable.RemoveColumn(column);
|
||||
}
|
||||
|
||||
CellRendererToggle favToggle = new CellRendererToggle();
|
||||
favToggle.Toggled += FavToggle_Toggled;
|
||||
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _gameTable.AppendColumn("Fav", favToggle, "active", 0); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.VersionColumn) { _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); }
|
||||
|
||||
foreach (TreeViewColumn column in _gameTable.Columns)
|
||||
{
|
||||
if (column.Title == "Fav") { _favColumn = column; }
|
||||
else if (column.Title == "Application") { _appColumn = column; }
|
||||
else if (column.Title == "Developer") { _devColumn = column; }
|
||||
else if (column.Title == "Version") { _versionColumn = column; }
|
||||
else if (column.Title == "Time Played") { _timePlayedColumn = column; }
|
||||
else if (column.Title == "Last Played") { _lastPlayedColumn = column; }
|
||||
else if (column.Title == "File Ext") { _fileExtColumn = column; }
|
||||
else if (column.Title == "File Size") { _fileSizeColumn = column; }
|
||||
else if (column.Title == "Path") { _pathColumn = column; }
|
||||
}
|
||||
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FavColumn) { _favColumn.SortColumnId = 0; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.IconColumn) { _appColumn.SortColumnId = 2; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.AppColumn) { _devColumn.SortColumnId = 3; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.DevColumn) { _versionColumn.SortColumnId = 4; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.TimePlayedColumn) { _timePlayedColumn.SortColumnId = 5; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.LastPlayedColumn) { _lastPlayedColumn.SortColumnId = 6; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileExtColumn) { _fileExtColumn.SortColumnId = 7; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.FileSizeColumn) { _fileSizeColumn.SortColumnId = 8; }
|
||||
if (SwitchSettings.SwitchConfig.GuiColumns.PathColumn) { _pathColumn.SortColumnId = 9; }
|
||||
}
|
||||
|
||||
public static void UpdateGameTable()
|
||||
internal static async Task UpdateGameTable()
|
||||
{
|
||||
if (_updatingGameTable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_updatingGameTable = true;
|
||||
|
||||
_tableStore.Clear();
|
||||
ApplicationLibrary.Init(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage);
|
||||
|
||||
foreach (ApplicationLibrary.ApplicationData AppData in ApplicationLibrary.ApplicationLibraryData)
|
||||
{
|
||||
_tableStore.AppendValues(new Gdk.Pixbuf(AppData.Icon, 75, 75), $"{AppData.TitleName}\n{AppData.TitleId.ToUpper()}", AppData.Developer, AppData.Version, AppData.TimePlayed, AppData.LastPlayed, AppData.FileExt, AppData.FileSize, AppData.Path);
|
||||
}
|
||||
}
|
||||
await Task.Run(() => ApplicationLibrary.LoadApplications(SwitchSettings.SwitchConfig.GameDirs, _device.System.KeySet, _device.System.State.DesiredTitleLanguage));
|
||||
|
||||
public static void ApplyTheme()
|
||||
{
|
||||
CssProvider cssProvider = new CssProvider();
|
||||
|
||||
if (SwitchSettings.SwitchConfig.EnableCustomTheme)
|
||||
{
|
||||
if (File.Exists(SwitchSettings.SwitchConfig.CustomThemePath) && (System.IO.Path.GetExtension(SwitchSettings.SwitchConfig.CustomThemePath) == ".css"))
|
||||
{
|
||||
cssProvider.LoadFromPath(SwitchSettings.SwitchConfig.CustomThemePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{SwitchSettings.SwitchConfig.CustomThemePath}\"");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cssProvider.LoadFromPath(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Theme.css"));
|
||||
}
|
||||
|
||||
StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800);
|
||||
_updatingGameTable = false;
|
||||
}
|
||||
|
||||
internal void LoadApplication(string path)
|
||||
{
|
||||
if (_gameLoaded)
|
||||
{
|
||||
CreateErrorDialog("A game has already been loaded. Please close the emulator and try again");
|
||||
GtkDialog.CreateErrorDialog("A game has already been loaded. Please close the emulator and try again");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -266,19 +304,23 @@ namespace Ryujinx.UI
|
||||
End();
|
||||
}
|
||||
|
||||
new Thread(new ThreadStart(CreateGameWindow)).Start();
|
||||
#if MACOS_BUILD
|
||||
CreateGameWindow();
|
||||
#else
|
||||
new Thread(CreateGameWindow).Start();
|
||||
#endif
|
||||
|
||||
_gameLoaded = true;
|
||||
_stopEmulation.Sensitive = true;
|
||||
|
||||
if (DiscordIntegrationEnabled)
|
||||
{
|
||||
if (File.ReadAllLines(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(_device.System.TitleID))
|
||||
if (File.ReadAllLines(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RPsupported.dat")).Contains(_device.System.TitleId))
|
||||
{
|
||||
DiscordPresence.Assets.LargeImageKey = _device.System.TitleID;
|
||||
DiscordPresence.Assets.LargeImageKey = _device.System.TitleId;
|
||||
}
|
||||
|
||||
string state = _device.System.TitleID;
|
||||
string state = _device.System.TitleId;
|
||||
|
||||
if (state == null)
|
||||
{
|
||||
@ -306,40 +348,37 @@ namespace Ryujinx.UI
|
||||
DiscordClient.SetPresence(DiscordPresence);
|
||||
}
|
||||
|
||||
try
|
||||
string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui");
|
||||
string metadataFile = System.IO.Path.Combine(metadataFolder, "metadata.json");
|
||||
|
||||
IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
|
||||
|
||||
ApplicationMetadata appMetadata;
|
||||
|
||||
if (!File.Exists(metadataFile))
|
||||
{
|
||||
string savePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", _userId, _device.System.TitleID);
|
||||
Directory.CreateDirectory(metadataFolder);
|
||||
|
||||
if (File.Exists(System.IO.Path.Combine(savePath, "TimePlayed.dat")) == false)
|
||||
appMetadata = new ApplicationMetadata
|
||||
{
|
||||
Directory.CreateDirectory(savePath);
|
||||
using (FileStream stream = File.OpenWrite(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
|
||||
{
|
||||
stream.Write(Encoding.ASCII.GetBytes("0"));
|
||||
}
|
||||
}
|
||||
Favorite = false,
|
||||
TimePlayed = 0,
|
||||
LastPlayed = "Never"
|
||||
};
|
||||
|
||||
if (File.Exists(System.IO.Path.Combine(savePath, "LastPlayed.dat")) == false)
|
||||
{
|
||||
Directory.CreateDirectory(savePath);
|
||||
using (FileStream stream = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
|
||||
{
|
||||
stream.Write(Encoding.ASCII.GetBytes("Never"));
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream stream = File.OpenWrite(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
|
||||
{
|
||||
using (StreamWriter writer = new StreamWriter(stream))
|
||||
{
|
||||
writer.WriteLine(DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
byte[] data = JsonSerializer.Serialize(appMetadata, resolver);
|
||||
File.WriteAllText(metadataFile, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson());
|
||||
}
|
||||
catch (ArgumentNullException)
|
||||
|
||||
using (Stream stream = File.OpenRead(metadataFile))
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"Could not access save path to retrieve time/last played data using: UserID: {_userId}, TitleID: {_device.System.TitleID}");
|
||||
appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
|
||||
}
|
||||
|
||||
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||
|
||||
byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver);
|
||||
File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,9 +386,9 @@ namespace Ryujinx.UI
|
||||
{
|
||||
Configuration.ConfigureHid(_device, SwitchSettings.SwitchConfig);
|
||||
|
||||
using (GlScreen screen = new GlScreen(_device, _renderer))
|
||||
using (_screen = new GlScreen(_device, _renderer))
|
||||
{
|
||||
screen.MainLoop();
|
||||
_screen.MainLoop();
|
||||
|
||||
End();
|
||||
}
|
||||
@ -357,41 +396,49 @@ namespace Ryujinx.UI
|
||||
|
||||
private static void End()
|
||||
{
|
||||
if (_ending)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ending = true;
|
||||
|
||||
if (_gameLoaded)
|
||||
{
|
||||
try
|
||||
{
|
||||
string savePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "nand", "user", "save", "0000000000000000", _userId, _device.System.TitleID);
|
||||
double currentPlayTime = 0;
|
||||
string metadataFolder = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", _device.System.TitleId, "gui");
|
||||
string metadataFile = System.IO.Path.Combine(metadataFolder, "metadata.json");
|
||||
|
||||
using (FileStream stream = File.OpenRead(System.IO.Path.Combine(savePath, "LastPlayed.dat")))
|
||||
IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
|
||||
|
||||
ApplicationMetadata appMetadata;
|
||||
|
||||
if (!File.Exists(metadataFile))
|
||||
{
|
||||
Directory.CreateDirectory(metadataFolder);
|
||||
|
||||
appMetadata = new ApplicationMetadata
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(stream))
|
||||
{
|
||||
DateTime startTime = DateTime.Parse(reader.ReadLine());
|
||||
Favorite = false,
|
||||
TimePlayed = 0,
|
||||
LastPlayed = "Never"
|
||||
};
|
||||
|
||||
using (FileStream lastPlayedStream = File.OpenRead(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
|
||||
{
|
||||
using (StreamReader lastPlayedReader = new StreamReader(lastPlayedStream))
|
||||
{
|
||||
currentPlayTime = double.Parse(lastPlayedReader.ReadLine());
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream timePlayedStream = File.OpenWrite(System.IO.Path.Combine(savePath, "TimePlayed.dat")))
|
||||
{
|
||||
using (StreamWriter timePlayedWriter = new StreamWriter(timePlayedStream))
|
||||
{
|
||||
timePlayedWriter.WriteLine(currentPlayTime + Math.Round(DateTime.UtcNow.Subtract(startTime).TotalSeconds, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
byte[] data = JsonSerializer.Serialize(appMetadata, resolver);
|
||||
File.WriteAllText(metadataFile, Encoding.UTF8.GetString(data, 0, data.Length).PrettyPrintJson());
|
||||
}
|
||||
catch (ArgumentNullException)
|
||||
|
||||
using (Stream stream = File.OpenRead(metadataFile))
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"Could not access save path to retrieve time/last played data using: UserID: {_userId}, TitleID: {_device.System.TitleID}");
|
||||
appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
|
||||
}
|
||||
|
||||
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
||||
|
||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||
|
||||
byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver);
|
||||
File.WriteAllText(metadataFile, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
|
||||
}
|
||||
|
||||
Profile.FinishProfiling();
|
||||
@ -423,15 +470,69 @@ namespace Ryujinx.UI
|
||||
}
|
||||
|
||||
//Events
|
||||
private void Row_Activated(object o, RowActivatedArgs args)
|
||||
private void Application_Added(object sender, ApplicationAddedEventArgs e)
|
||||
{
|
||||
Application.Invoke(delegate
|
||||
{
|
||||
_tableStore.AppendValues(
|
||||
e.AppData.Favorite,
|
||||
new Gdk.Pixbuf(e.AppData.Icon, 75, 75),
|
||||
$"{e.AppData.TitleName}\n{e.AppData.TitleId.ToUpper()}",
|
||||
e.AppData.Developer,
|
||||
e.AppData.Version,
|
||||
e.AppData.TimePlayed,
|
||||
e.AppData.LastPlayed,
|
||||
e.AppData.FileExtension,
|
||||
e.AppData.FileSize,
|
||||
e.AppData.Path);
|
||||
|
||||
_progressLabel.Text = $"{e.NumAppsLoaded}/{e.NumAppsFound} Games Loaded";
|
||||
_progressBar.Value = (float)e.NumAppsLoaded / e.NumAppsFound;
|
||||
});
|
||||
}
|
||||
|
||||
private void FavToggle_Toggled(object sender, ToggledArgs args)
|
||||
{
|
||||
_tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path));
|
||||
|
||||
string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
|
||||
string metadataPath = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "games", titleId, "gui", "metadata.json");
|
||||
|
||||
IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
|
||||
|
||||
ApplicationMetadata appMetadata;
|
||||
|
||||
using (Stream stream = File.OpenRead(metadataPath))
|
||||
{
|
||||
appMetadata = JsonSerializer.Deserialize<ApplicationMetadata>(stream, resolver);
|
||||
}
|
||||
|
||||
if ((bool)_tableStore.GetValue(treeIter, 0))
|
||||
{
|
||||
_tableStore.SetValue(treeIter, 0, false);
|
||||
|
||||
appMetadata.Favorite = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_tableStore.SetValue(treeIter, 0, true);
|
||||
|
||||
appMetadata.Favorite = true;
|
||||
}
|
||||
|
||||
byte[] saveData = JsonSerializer.Serialize(appMetadata, resolver);
|
||||
File.WriteAllText(metadataPath, Encoding.UTF8.GetString(saveData, 0, saveData.Length).PrettyPrintJson());
|
||||
}
|
||||
|
||||
private void Row_Activated(object sender, RowActivatedArgs args)
|
||||
{
|
||||
_tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path.ToString()));
|
||||
string path = (string)_tableStore.GetValue(treeIter, 8);
|
||||
string path = (string)_tableStore.GetValue(treeIter, 9);
|
||||
|
||||
LoadApplication(path);
|
||||
}
|
||||
|
||||
private void Load_Application_File(object o, EventArgs args)
|
||||
private void Load_Application_File(object sender, EventArgs args)
|
||||
{
|
||||
FileChooserDialog fileChooser = new FileChooserDialog("Choose the file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
|
||||
|
||||
@ -448,10 +549,10 @@ namespace Ryujinx.UI
|
||||
LoadApplication(fileChooser.Filename);
|
||||
}
|
||||
|
||||
fileChooser.Destroy();
|
||||
fileChooser.Dispose();
|
||||
}
|
||||
|
||||
private void Load_Application_Folder(object o, EventArgs args)
|
||||
private void Load_Application_Folder(object sender, EventArgs args)
|
||||
{
|
||||
FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to open", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
|
||||
|
||||
@ -460,35 +561,39 @@ namespace Ryujinx.UI
|
||||
LoadApplication(fileChooser.Filename);
|
||||
}
|
||||
|
||||
fileChooser.Destroy();
|
||||
fileChooser.Dispose();
|
||||
}
|
||||
|
||||
private void Open_Ryu_Folder(object o, EventArgs args)
|
||||
private void Open_Ryu_Folder(object sender, EventArgs args)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFs"),
|
||||
FileName = new VirtualFileSystem().GetBasePath(),
|
||||
UseShellExecute = true,
|
||||
Verb = "open"
|
||||
});
|
||||
}
|
||||
|
||||
private void Exit_Pressed(object o, EventArgs args)
|
||||
private void Exit_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
_screen?.Exit();
|
||||
End();
|
||||
}
|
||||
|
||||
private void Window_Close(object o, DeleteEventArgs args)
|
||||
private void Window_Close(object sender, DeleteEventArgs args)
|
||||
{
|
||||
_screen?.Exit();
|
||||
End();
|
||||
}
|
||||
|
||||
private void StopEmulation_Pressed(object o, EventArgs args)
|
||||
private void StopEmulation_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
// TODO: Write logic to kill running game
|
||||
|
||||
_gameLoaded = false;
|
||||
}
|
||||
|
||||
private void FullScreen_Toggled(object o, EventArgs args)
|
||||
private void FullScreen_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
if (_fullScreen.Active)
|
||||
{
|
||||
@ -500,19 +605,15 @@ namespace Ryujinx.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void Settings_Pressed(object o, EventArgs args)
|
||||
private void Settings_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings SettingsWin = new SwitchSettings(_device);
|
||||
|
||||
_gtkApplication.Register(GLib.Cancellable.Current);
|
||||
_gtkApplication.AddWindow(SettingsWin);
|
||||
|
||||
SettingsWin.Show();
|
||||
SwitchSettings settingsWin = new SwitchSettings(_device);
|
||||
settingsWin.Show();
|
||||
}
|
||||
|
||||
private void Update_Pressed(object o, EventArgs args)
|
||||
private void Update_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
string ryuUpdater = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "RyuFS", "RyuUpdater.exe");
|
||||
string ryuUpdater = System.IO.Path.Combine(new VirtualFileSystem().GetBasePath(), "RyuUpdater.exe");
|
||||
|
||||
try
|
||||
{
|
||||
@ -520,81 +621,249 @@ namespace Ryujinx.UI
|
||||
}
|
||||
catch(System.ComponentModel.Win32Exception)
|
||||
{
|
||||
CreateErrorDialog("Update canceled by user or updater was not found");
|
||||
GtkDialog.CreateErrorDialog("Update canceled by user or updater was not found");
|
||||
}
|
||||
}
|
||||
|
||||
private void About_Pressed(object o, EventArgs args)
|
||||
private void About_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
AboutWindow AboutWin = new AboutWindow();
|
||||
|
||||
_gtkApplication.Register(GLib.Cancellable.Current);
|
||||
_gtkApplication.AddWindow(AboutWin);
|
||||
|
||||
AboutWin.Show();
|
||||
AboutWindow aboutWin = new AboutWindow();
|
||||
aboutWin.Show();
|
||||
}
|
||||
|
||||
private void Icon_Toggled(object o, EventArgs args)
|
||||
private void Fav_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[0] = _iconToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.FavColumn = _favToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void Title_Toggled(object o, EventArgs args)
|
||||
private void Icon_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[1] = _titleToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.IconColumn = _iconToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void Developer_Toggled(object o, EventArgs args)
|
||||
private void Title_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[2] = _developerToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.AppColumn = _appToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void Version_Toggled(object o, EventArgs args)
|
||||
private void Developer_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[3] = _versionToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.DevColumn = _developerToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void TimePlayed_Toggled(object o, EventArgs args)
|
||||
private void Version_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[4] = _timePlayedToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.VersionColumn = _versionToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void LastPlayed_Toggled(object o, EventArgs args)
|
||||
private void TimePlayed_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[5] = _lastPlayedToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.TimePlayedColumn = _timePlayedToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void FileExt_Toggled(object o, EventArgs args)
|
||||
private void LastPlayed_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[6] = _fileExtToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.LastPlayedColumn = _lastPlayedToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void FileSize_Toggled(object o, EventArgs args)
|
||||
private void FileExt_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[7] = _fileSizeToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.FileExtColumn = _fileExtToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void Path_Toggled(object o, EventArgs args)
|
||||
private void FileSize_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
SwitchSettings.SwitchConfig.GuiColumns[8] = _pathToggle.Active;
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.FileSizeColumn = _fileSizeToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void Path_Toggled(object sender, EventArgs args)
|
||||
{
|
||||
GuiColumns updatedColumns = SwitchSettings.SwitchConfig.GuiColumns;
|
||||
|
||||
updatedColumns.PathColumn = _pathToggle.Active;
|
||||
SwitchSettings.SwitchConfig.GuiColumns = updatedColumns;
|
||||
|
||||
Configuration.SaveConfig(SwitchSettings.SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
|
||||
UpdateColumns();
|
||||
}
|
||||
|
||||
private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args)
|
||||
{
|
||||
#pragma warning disable CS4014
|
||||
UpdateGameTable();
|
||||
#pragma warning restore CS4014
|
||||
}
|
||||
|
||||
private static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
||||
{
|
||||
string aValue = model.GetValue(a, 5).ToString();
|
||||
string bValue = model.GetValue(b, 5).ToString();
|
||||
|
||||
if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "mins")
|
||||
{
|
||||
aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 60).ToString();
|
||||
}
|
||||
else if (aValue.Length > 3 && aValue.Substring(aValue.Length - 3) == "hrs")
|
||||
{
|
||||
aValue = (float.Parse(aValue.Substring(0, aValue.Length - 4)) * 3600).ToString();
|
||||
}
|
||||
else if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "days")
|
||||
{
|
||||
aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 86400).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
aValue = aValue.Substring(0, aValue.Length - 1);
|
||||
}
|
||||
|
||||
if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "mins")
|
||||
{
|
||||
bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 60).ToString();
|
||||
}
|
||||
else if (bValue.Length > 3 && bValue.Substring(bValue.Length - 3) == "hrs")
|
||||
{
|
||||
bValue = (float.Parse(bValue.Substring(0, bValue.Length - 4)) * 3600).ToString();
|
||||
}
|
||||
else if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "days")
|
||||
{
|
||||
bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 86400).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
bValue = bValue.Substring(0, bValue.Length - 1);
|
||||
}
|
||||
|
||||
if (float.Parse(aValue) > float.Parse(bValue))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (float.Parse(bValue) > float.Parse(aValue))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
||||
{
|
||||
string aValue = model.GetValue(a, 6).ToString();
|
||||
string bValue = model.GetValue(b, 6).ToString();
|
||||
|
||||
if (aValue == "Never")
|
||||
{
|
||||
aValue = DateTime.UnixEpoch.ToString();
|
||||
}
|
||||
|
||||
if (bValue == "Never")
|
||||
{
|
||||
bValue = DateTime.UnixEpoch.ToString();
|
||||
}
|
||||
|
||||
return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
|
||||
}
|
||||
|
||||
private static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b)
|
||||
{
|
||||
string aValue = model.GetValue(a, 8).ToString();
|
||||
string bValue = model.GetValue(b, 8).ToString();
|
||||
|
||||
if (aValue.Substring(aValue.Length - 2) == "GB")
|
||||
{
|
||||
aValue = (float.Parse(aValue[0..^2]) * 1024).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
aValue = aValue[0..^2];
|
||||
}
|
||||
|
||||
if (bValue.Substring(bValue.Length - 2) == "GB")
|
||||
{
|
||||
bValue = (float.Parse(bValue[0..^2]) * 1024).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
bValue = bValue[0..^2];
|
||||
}
|
||||
|
||||
if (float.Parse(aValue) > float.Parse(bValue))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (float.Parse(bValue) > float.Parse(aValue))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,13 +126,23 @@
|
||||
<object class="GtkMenuItem" id="GUIColumns">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Select which GUI columns to enable (restart Ryujinx for these changes to take effect)</property>
|
||||
<property name="tooltip_text" translatable="yes">Select which GUI columns to enable</property>
|
||||
<property name="label" translatable="yes">Enable GUI Columns</property>
|
||||
<property name="use_underline">True</property>
|
||||
<child type="submenu">
|
||||
<object class="GtkMenu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkCheckMenuItem" id="_favToggle">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Enable or Disable Favorite Games Column in the game list</property>
|
||||
<property name="label" translatable="yes">Enable Favorite Games Column</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="toggled" handler="Fav_Toggled" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckMenuItem" id="_iconToggle">
|
||||
<property name="visible">True</property>
|
||||
@ -144,7 +154,7 @@
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckMenuItem" id="_titleToggle">
|
||||
<object class="GtkCheckMenuItem" id="_appToggle">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Enable or Disable Title Name/ID Column in the game list</property>
|
||||
@ -303,22 +313,96 @@
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="_gameTableWindow">
|
||||
<object class="GtkBox" id="MainBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="_gameTable">
|
||||
<object class="GtkScrolledWindow" id="_gameTableWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_clickable">False</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="hover_selection">True</property>
|
||||
<signal name="row-activated" handler="Row_Activated" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="_gameTable">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="hover_selection">True</property>
|
||||
<signal name="row-activated" handler="Row_Activated" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="FooterBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkEventBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<signal name="button-release-event" handler="RefreshList_Pressed" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="name">RefreshList</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-refresh</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="_progressLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<property name="label" translatable="yes">0/0 Games Loaded</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLevelBar" id="_progressBar">
|
||||
<property name="width_request">200</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
@ -327,20 +411,6 @@
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGLArea" id="_glScreen">
|
||||
<property name="width_request">1280</property>
|
||||
<property name="height_request">720</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="app_paintable">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -3,7 +3,7 @@ using OpenTK.Input;
|
||||
using Ryujinx.HLE.Input;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.UI.Input
|
||||
namespace Ryujinx.Ui.Input
|
||||
{
|
||||
public enum ControllerInputId
|
||||
{
|
||||
@ -64,7 +64,6 @@ namespace Ryujinx.UI.Input
|
||||
public struct NpadControllerRight
|
||||
{
|
||||
public ControllerInputId Stick;
|
||||
public ControllerInputId StickY;
|
||||
public ControllerInputId StickButton;
|
||||
public ControllerInputId ButtonA;
|
||||
public ControllerInputId ButtonB;
|
||||
|
@ -1,7 +1,7 @@
|
||||
using OpenTK.Input;
|
||||
using Ryujinx.HLE.Input;
|
||||
|
||||
namespace Ryujinx.UI.Input
|
||||
namespace Ryujinx.Ui.Input
|
||||
{
|
||||
public struct NpadKeyboardLeft
|
||||
{
|
||||
|
@ -1,27 +1,29 @@
|
||||
using Gtk;
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
using Ryujinx.HLE.HOS.SystemState;
|
||||
using Ryujinx.HLE.Input;
|
||||
using Ryujinx.UI.Input;
|
||||
using Ryujinx.Ui.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.UI
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class SwitchSettings : Window
|
||||
{
|
||||
internal static Configuration SwitchConfig { get; set; }
|
||||
|
||||
internal HLE.Switch Device { get; set; }
|
||||
private readonly HLE.Switch _device;
|
||||
|
||||
private static ListStore _gameDirsBoxStore;
|
||||
|
||||
private static bool _listeningForKeypress;
|
||||
|
||||
#pragma warning disable 649
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable IDE0044
|
||||
[GUI] Window _settingsWin;
|
||||
[GUI] CheckButton _errorLogToggle;
|
||||
[GUI] CheckButton _warningLogToggle;
|
||||
@ -51,7 +53,7 @@ namespace Ryujinx.UI
|
||||
[GUI] ToggleButton _removeDir;
|
||||
[GUI] Entry _logPath;
|
||||
[GUI] Entry _graphicsShadersDumpPath;
|
||||
[GUI] Image _controllerImage;
|
||||
[GUI] Image _controller1Image;
|
||||
|
||||
[GUI] ComboBoxText _controller1Type;
|
||||
[GUI] ToggleButton _lStickUp1;
|
||||
@ -78,67 +80,70 @@ namespace Ryujinx.UI
|
||||
[GUI] ToggleButton _plus1;
|
||||
[GUI] ToggleButton _r1;
|
||||
[GUI] ToggleButton _zR1;
|
||||
#pragma warning restore 649
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore IDE0044
|
||||
|
||||
public static void ConfigureSettings(Configuration Instance) { SwitchConfig = Instance; }
|
||||
public static void ConfigureSettings(Configuration instance) { SwitchConfig = instance; }
|
||||
|
||||
public SwitchSettings(HLE.Switch device) : this(new Builder("Ryujinx.Ui.SwitchSettings.glade"), device) { }
|
||||
|
||||
private SwitchSettings(Builder builder, HLE.Switch device) : base(builder.GetObject("_settingsWin").Handle)
|
||||
{
|
||||
Device = device;
|
||||
|
||||
builder.Autoconnect(this);
|
||||
|
||||
_settingsWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ryujinxIcon.png");
|
||||
_controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500);
|
||||
_device = device;
|
||||
|
||||
_settingsWin.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
|
||||
_controller1Image.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500);
|
||||
|
||||
//Bind Events
|
||||
_lStickUp1.Clicked += (o, args) => Button_Pressed(o, args, _lStickUp1);
|
||||
_lStickDown1.Clicked += (o, args) => Button_Pressed(o, args, _lStickDown1);
|
||||
_lStickLeft1.Clicked += (o, args) => Button_Pressed(o, args, _lStickLeft1);
|
||||
_lStickRight1.Clicked += (o, args) => Button_Pressed(o, args, _lStickRight1);
|
||||
_lStickButton1.Clicked += (o, args) => Button_Pressed(o, args, _lStickButton1);
|
||||
_dpadUp1.Clicked += (o, args) => Button_Pressed(o, args, _dpadUp1);
|
||||
_dpadDown1.Clicked += (o, args) => Button_Pressed(o, args, _dpadDown1);
|
||||
_dpadLeft1.Clicked += (o, args) => Button_Pressed(o, args, _dpadLeft1);
|
||||
_dpadRight1.Clicked += (o, args) => Button_Pressed(o, args, _dpadRight1);
|
||||
_minus1.Clicked += (o, args) => Button_Pressed(o, args, _minus1);
|
||||
_l1.Clicked += (o, args) => Button_Pressed(o, args, _l1);
|
||||
_zL1.Clicked += (o, args) => Button_Pressed(o, args, _zL1);
|
||||
_rStickUp1.Clicked += (o, args) => Button_Pressed(o, args, _rStickUp1);
|
||||
_rStickDown1.Clicked += (o, args) => Button_Pressed(o, args, _rStickDown1);
|
||||
_rStickLeft1.Clicked += (o, args) => Button_Pressed(o, args, _rStickLeft1);
|
||||
_rStickRight1.Clicked += (o, args) => Button_Pressed(o, args, _rStickRight1);
|
||||
_rStickButton1.Clicked += (o, args) => Button_Pressed(o, args, _rStickButton1);
|
||||
_a1.Clicked += (o, args) => Button_Pressed(o, args, _a1);
|
||||
_b1.Clicked += (o, args) => Button_Pressed(o, args, _b1);
|
||||
_x1.Clicked += (o, args) => Button_Pressed(o, args, _x1);
|
||||
_y1.Clicked += (o, args) => Button_Pressed(o, args, _y1);
|
||||
_plus1.Clicked += (o, args) => Button_Pressed(o, args, _plus1);
|
||||
_r1.Clicked += (o, args) => Button_Pressed(o, args, _r1);
|
||||
_zR1.Clicked += (o, args) => Button_Pressed(o, args, _zR1);
|
||||
_lStickUp1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickUp1);
|
||||
_lStickDown1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickDown1);
|
||||
_lStickLeft1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickLeft1);
|
||||
_lStickRight1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickRight1);
|
||||
_lStickButton1.Clicked += (sender, args) => Button_Pressed(sender, args, _lStickButton1);
|
||||
_dpadUp1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadUp1);
|
||||
_dpadDown1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadDown1);
|
||||
_dpadLeft1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadLeft1);
|
||||
_dpadRight1.Clicked += (sender, args) => Button_Pressed(sender, args, _dpadRight1);
|
||||
_minus1.Clicked += (sender, args) => Button_Pressed(sender, args, _minus1);
|
||||
_l1.Clicked += (sender, args) => Button_Pressed(sender, args, _l1);
|
||||
_zL1.Clicked += (sender, args) => Button_Pressed(sender, args, _zL1);
|
||||
_rStickUp1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickUp1);
|
||||
_rStickDown1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickDown1);
|
||||
_rStickLeft1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickLeft1);
|
||||
_rStickRight1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickRight1);
|
||||
_rStickButton1.Clicked += (sender, args) => Button_Pressed(sender, args, _rStickButton1);
|
||||
_a1.Clicked += (sender, args) => Button_Pressed(sender, args, _a1);
|
||||
_b1.Clicked += (sender, args) => Button_Pressed(sender, args, _b1);
|
||||
_x1.Clicked += (sender, args) => Button_Pressed(sender, args, _x1);
|
||||
_y1.Clicked += (sender, args) => Button_Pressed(sender, args, _y1);
|
||||
_plus1.Clicked += (sender, args) => Button_Pressed(sender, args, _plus1);
|
||||
_r1.Clicked += (sender, args) => Button_Pressed(sender, args, _r1);
|
||||
_zR1.Clicked += (sender, args) => Button_Pressed(sender, args, _zR1);
|
||||
_controller1Type.Changed += (sender, args) => Controller_Changed(sender, args, _controller1Type.ActiveId, _controller1Image);
|
||||
|
||||
//Setup Currents
|
||||
if (SwitchConfig.EnableFileLog) { _fileLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableError) { _errorLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableWarn) { _warningLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableInfo) { _infoLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableStub) { _stubLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableDebug) { _debugLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableGuest) { _guestLogToggle.Click(); }
|
||||
if (SwitchConfig.LoggingEnableFsAccessLog) { _fsAccessLogToggle.Click(); }
|
||||
if (SwitchConfig.DockedMode) { _dockedModeToggle.Click(); }
|
||||
if (SwitchConfig.EnableDiscordIntegration) { _discordToggle.Click(); }
|
||||
if (SwitchConfig.EnableVsync) { _vSyncToggle.Click(); }
|
||||
if (SwitchConfig.EnableMulticoreScheduling) { _multiSchedToggle.Click(); }
|
||||
if (SwitchConfig.EnableFsIntegrityChecks) { _fsicToggle.Click(); }
|
||||
if (SwitchConfig.IgnoreMissingServices) { _ignoreToggle.Click(); }
|
||||
if (SwitchConfig.EnableKeyboard) { _directKeyboardAccess.Click(); }
|
||||
if (SwitchConfig.EnableCustomTheme) { _custThemeToggle.Click(); }
|
||||
if (SwitchConfig.EnableFileLog) _fileLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableError) _errorLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableWarn) _warningLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableInfo) _infoLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableStub) _stubLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableDebug) _debugLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableGuest) _guestLogToggle.Click();
|
||||
if (SwitchConfig.LoggingEnableFsAccessLog) _fsAccessLogToggle.Click();
|
||||
if (SwitchConfig.DockedMode) _dockedModeToggle.Click();
|
||||
if (SwitchConfig.EnableDiscordIntegration) _discordToggle.Click();
|
||||
if (SwitchConfig.EnableVsync) _vSyncToggle.Click();
|
||||
if (SwitchConfig.EnableMulticoreScheduling) _multiSchedToggle.Click();
|
||||
if (SwitchConfig.EnableFsIntegrityChecks) _fsicToggle.Click();
|
||||
if (SwitchConfig.IgnoreMissingServices) _ignoreToggle.Click();
|
||||
if (SwitchConfig.EnableKeyboard) _directKeyboardAccess.Click();
|
||||
if (SwitchConfig.EnableCustomTheme) _custThemeToggle.Click();
|
||||
|
||||
_systemLanguageSelect.SetActiveId(SwitchConfig.SystemLanguage.ToString());
|
||||
_controller1Type .SetActiveId(SwitchConfig.ControllerType.ToString());
|
||||
Controller_Changed(null, null, _controller1Type.ActiveId, _controller1Image);
|
||||
|
||||
_lStickUp1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickUp.ToString();
|
||||
_lStickDown1.Label = SwitchConfig.KeyboardControls.LeftJoycon.StickDown.ToString();
|
||||
@ -190,7 +195,7 @@ namespace Ryujinx.UI
|
||||
}
|
||||
|
||||
//Events
|
||||
private void Button_Pressed(object obj, EventArgs args, ToggleButton Button)
|
||||
private void Button_Pressed(object sender, EventArgs args, ToggleButton button)
|
||||
{
|
||||
if (_listeningForKeypress == false)
|
||||
{
|
||||
@ -198,25 +203,25 @@ namespace Ryujinx.UI
|
||||
|
||||
_listeningForKeypress = true;
|
||||
|
||||
void On_KeyPress(object Obj, KeyPressEventArgs KeyPressed)
|
||||
void On_KeyPress(object o, KeyPressEventArgs keyPressed)
|
||||
{
|
||||
string key = KeyPressed.Event.Key.ToString();
|
||||
string key = keyPressed.Event.Key.ToString();
|
||||
string capKey = key.First().ToString().ToUpper() + key.Substring(1);
|
||||
|
||||
if (Enum.IsDefined(typeof(OpenTK.Input.Key), capKey))
|
||||
{
|
||||
Button.Label = capKey;
|
||||
button.Label = capKey;
|
||||
}
|
||||
else if (GdkToOpenTKInput.ContainsKey(key))
|
||||
else if (GdkToOpenTkInput.ContainsKey(key))
|
||||
{
|
||||
Button.Label = GdkToOpenTKInput[key];
|
||||
button.Label = GdkToOpenTkInput[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
Button.Label = "Space";
|
||||
button.Label = "Space";
|
||||
}
|
||||
|
||||
Button.SetStateFlags(0, true);
|
||||
button.SetStateFlags(0, true);
|
||||
|
||||
KeyPressEvent -= On_KeyPress;
|
||||
|
||||
@ -225,11 +230,30 @@ namespace Ryujinx.UI
|
||||
}
|
||||
else
|
||||
{
|
||||
Button.SetStateFlags(0, true);
|
||||
button.SetStateFlags(0, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDir_Pressed(object obj, EventArgs args)
|
||||
private void Controller_Changed(object sender, EventArgs args, string controllerType, Image controllerImage)
|
||||
{
|
||||
switch (controllerType)
|
||||
{
|
||||
case "ProController":
|
||||
controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.ProCon.png", 500, 500);
|
||||
break;
|
||||
case "NpadLeft":
|
||||
controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.BlueCon.png", 500, 500);
|
||||
break;
|
||||
case "NpadRight":
|
||||
controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.RedCon.png", 500, 500);
|
||||
break;
|
||||
default:
|
||||
controllerImage.Pixbuf = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.JoyCon.png", 500, 500);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDir_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
if (Directory.Exists(_addGameDirBox.Buffer.Text))
|
||||
{
|
||||
@ -239,7 +263,7 @@ namespace Ryujinx.UI
|
||||
_addDir.SetStateFlags(0, true);
|
||||
}
|
||||
|
||||
private void BrowseDir_Pressed(object obj, EventArgs args)
|
||||
private void BrowseDir_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
FileChooserDialog fileChooser = new FileChooserDialog("Choose the game directory to add to the list", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept);
|
||||
|
||||
@ -248,12 +272,12 @@ namespace Ryujinx.UI
|
||||
_gameDirsBoxStore.AppendValues(fileChooser.Filename);
|
||||
}
|
||||
|
||||
fileChooser.Destroy();
|
||||
fileChooser.Dispose();
|
||||
|
||||
_browseDir.SetStateFlags(0, true);
|
||||
}
|
||||
|
||||
private void RemoveDir_Pressed(object obj, EventArgs args)
|
||||
private void RemoveDir_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
TreeSelection selection = _gameDirsBox.Selection;
|
||||
|
||||
@ -263,14 +287,14 @@ namespace Ryujinx.UI
|
||||
_removeDir.SetStateFlags(0, true);
|
||||
}
|
||||
|
||||
private void CustThemeToggle_Activated(object obj, EventArgs args)
|
||||
private void CustThemeToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
_custThemePath.Sensitive = _custThemeToggle.Active;
|
||||
_custThemePathLabel.Sensitive = _custThemeToggle.Active;
|
||||
_browseThemePath.Sensitive = _custThemeToggle.Active;
|
||||
}
|
||||
|
||||
private void BrowseThemeDir_Pressed(object obj, EventArgs args)
|
||||
private void BrowseThemeDir_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept);
|
||||
|
||||
@ -282,12 +306,12 @@ namespace Ryujinx.UI
|
||||
_custThemePath.Buffer.Text = fileChooser.Filename;
|
||||
}
|
||||
|
||||
fileChooser.Destroy();
|
||||
fileChooser.Dispose();
|
||||
|
||||
_browseThemePath.SetStateFlags(0, true);
|
||||
}
|
||||
|
||||
private void SaveToggle_Activated(object obj, EventArgs args)
|
||||
private void SaveToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
List<string> gameDirs = new List<string>();
|
||||
|
||||
@ -358,20 +382,21 @@ namespace Ryujinx.UI
|
||||
SwitchConfig.FsGlobalAccessLogMode = (int)_fsLogSpinAdjustment.Value;
|
||||
|
||||
Configuration.SaveConfig(SwitchConfig, System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"));
|
||||
Configuration.Configure(Device, SwitchConfig);
|
||||
Configuration.Configure(_device, SwitchConfig);
|
||||
|
||||
MainWindow.ApplyTheme();
|
||||
#pragma warning disable CS4014
|
||||
MainWindow.UpdateGameTable();
|
||||
|
||||
Destroy();
|
||||
#pragma warning restore CS4014
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CloseToggle_Activated(object obj, EventArgs args)
|
||||
private void CloseToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
Destroy();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public readonly Dictionary<string, string> GdkToOpenTKInput = new Dictionary<string, string>()
|
||||
public readonly Dictionary<string, string> GdkToOpenTkInput = new Dictionary<string, string>()
|
||||
{
|
||||
{ "Key_0", "Number0" },
|
||||
{ "Key_1", "Number1" },
|
||||
|
BIN
Ryujinx/Ui/assets/BlueCon.png
Normal file
After Width: | Height: | Size: 161 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 282 KiB After Width: | Height: | Size: 324 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 15 KiB |
BIN
Ryujinx/Ui/assets/ProCon.png
Normal file
After Width: | Height: | Size: 317 KiB |
BIN
Ryujinx/Ui/assets/RedCon.png
Normal file
After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@ -484,17 +484,28 @@
|
||||
},
|
||||
"game_dirs": {
|
||||
"$id": "#/properties/game_dirs",
|
||||
"type": "string list",
|
||||
"type": "array",
|
||||
"title": "List of Game Directories",
|
||||
"description": "A list of directories containing games to be used to load games into the games list",
|
||||
"default": []
|
||||
},
|
||||
"gui_columns": {
|
||||
"$id": "#/properties/gui_columns",
|
||||
"type": "bool list",
|
||||
"type": "array",
|
||||
"title": "Used to toggle columns in the GUI",
|
||||
"description": "Used to toggle columns in the GUI",
|
||||
"default": [ true, true, true, true, true, true, true, true, true ]
|
||||
"default": {
|
||||
"fav_column": true,
|
||||
"icon_column": true,
|
||||
"app_column": true,
|
||||
"dev_column": true,
|
||||
"version_column": true,
|
||||
"time_played_column": true,
|
||||
"last_played_column": true,
|
||||
"file_ext_column": true,
|
||||
"file_size_column": true,
|
||||
"path_column": true
|
||||
}
|
||||
},
|
||||
"enable_custom_theme": {
|
||||
"$id": "#/properties/enable_custom_theme",
|
||||
|