gui: Refactoring Part 1 (#1859)
* gui: Refactoring Part 1 * Fix ProfileDialog.glade path * Fix Application.Quit assert * Fix TitleUpdateWindow parent * Fix TitleUpdate selected item * Remove extra line in TitleUpdateWindow * Fix empty assign of Enum.TryParse * Add Patrons list in the About Window * update about error messages
This commit is contained in:
parent
72e94bb089
commit
a9cb31e75f
71 changed files with 1979 additions and 2549 deletions
467
Ryujinx/Ui/Windows/AboutWindow.Designer.cs
generated
Normal file
467
Ryujinx/Ui/Windows/AboutWindow.Designer.cs
generated
Normal file
|
@ -0,0 +1,467 @@
|
|||
using Gtk;
|
||||
using Pango;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.Ui.Windows
|
||||
{
|
||||
public partial class AboutWindow : Window
|
||||
{
|
||||
private Box _mainBox;
|
||||
private Box _leftBox;
|
||||
private Box _logoBox;
|
||||
private Image _ryujinxLogo;
|
||||
private Box _logoTextBox;
|
||||
private Label _ryujinxLabel;
|
||||
private Label _ryujinxPhoneticLabel;
|
||||
private EventBox _ryujinxLink;
|
||||
private Label _ryujinxLinkLabel;
|
||||
private Label _versionLabel;
|
||||
private Label _disclaimerLabel;
|
||||
private Box _socialBox;
|
||||
private EventBox _patreonEventBox;
|
||||
private Box _patreonBox;
|
||||
private Image _patreonLogo;
|
||||
private Label _patreonLabel;
|
||||
private EventBox _githubEventBox;
|
||||
private Box _githubBox;
|
||||
private Image _githubLogo;
|
||||
private Label _githubLabel;
|
||||
private Box _discordBox;
|
||||
private EventBox _discordEventBox;
|
||||
private Image _discordLogo;
|
||||
private Label _discordLabel;
|
||||
private EventBox _twitterEventBox;
|
||||
private Box _twitterBox;
|
||||
private Image _twitterLogo;
|
||||
private Label _twitterLabel;
|
||||
private Separator _separator;
|
||||
private Box _rightBox;
|
||||
private Label _aboutLabel;
|
||||
private Label _aboutDescriptionLabel;
|
||||
private Label _createdByLabel;
|
||||
private TextView _createdByText;
|
||||
private EventBox _contributorsEventBox;
|
||||
private Label _contributorsLinkLabel;
|
||||
private Label _patreonNamesLabel;
|
||||
private ScrolledWindow _patreonNamesScrolled;
|
||||
private TextView _patreonNamesText;
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
|
||||
#pragma warning disable CS0612
|
||||
|
||||
//
|
||||
// AboutWindow
|
||||
//
|
||||
CanFocus = false;
|
||||
Resizable = false;
|
||||
Modal = true;
|
||||
WindowPosition = WindowPosition.Center;
|
||||
DefaultWidth = 800;
|
||||
DefaultHeight = 450;
|
||||
TypeHint = Gdk.WindowTypeHint.Dialog;
|
||||
|
||||
//
|
||||
// _mainBox
|
||||
//
|
||||
_mainBox = new Box(Orientation.Horizontal, 0);
|
||||
|
||||
//
|
||||
// _leftBox
|
||||
//
|
||||
_leftBox = new Box(Orientation.Vertical, 0)
|
||||
{
|
||||
Margin = 15,
|
||||
MarginLeft = 30,
|
||||
MarginRight = 0
|
||||
};
|
||||
|
||||
//
|
||||
// _logoBox
|
||||
//
|
||||
_logoBox = new Box(Orientation.Horizontal, 0);
|
||||
|
||||
//
|
||||
// _ryujinxLogo
|
||||
//
|
||||
_ryujinxLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Ryujinx.png", 100, 100))
|
||||
{
|
||||
Margin = 10,
|
||||
MarginLeft = 15
|
||||
};
|
||||
|
||||
//
|
||||
// _logoTextBox
|
||||
//
|
||||
_logoTextBox = new Box(Orientation.Vertical, 0);
|
||||
|
||||
//
|
||||
// _ryujinxLabel
|
||||
//
|
||||
_ryujinxLabel = new Label("Ryujinx")
|
||||
{
|
||||
MarginTop = 15,
|
||||
Justify = Justification.Center,
|
||||
Attributes = new AttrList()
|
||||
};
|
||||
_ryujinxLabel.Attributes.Insert(new Pango.AttrScale(2.7f));
|
||||
|
||||
//
|
||||
// _ryujinxPhoneticLabel
|
||||
//
|
||||
_ryujinxPhoneticLabel = new Label("(REE-YOU-JI-NX)")
|
||||
{
|
||||
Justify = Justification.Center
|
||||
};
|
||||
|
||||
//
|
||||
// _ryujinxLink
|
||||
//
|
||||
_ryujinxLink = new EventBox()
|
||||
{
|
||||
Margin = 5
|
||||
};
|
||||
_ryujinxLink.ButtonPressEvent += RyujinxButton_Pressed;
|
||||
|
||||
//
|
||||
// _ryujinxLinkLabel
|
||||
//
|
||||
_ryujinxLinkLabel = new Label("www.ryujinx.org")
|
||||
{
|
||||
TooltipText = "Click to open the Ryujinx website in your default browser.",
|
||||
Justify = Justification.Center,
|
||||
Attributes = new AttrList()
|
||||
};
|
||||
_ryujinxLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
|
||||
|
||||
//
|
||||
// _versionLabel
|
||||
//
|
||||
_versionLabel = new Label(Program.Version)
|
||||
{
|
||||
Expand = true,
|
||||
Justify = Justification.Center,
|
||||
Margin = 5
|
||||
};
|
||||
|
||||
//
|
||||
// _disclaimerLabel
|
||||
//
|
||||
_disclaimerLabel = new Label("Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.")
|
||||
{
|
||||
Expand = true,
|
||||
Justify = Justification.Center,
|
||||
Margin = 5,
|
||||
Attributes = new AttrList()
|
||||
};
|
||||
_disclaimerLabel.Attributes.Insert(new Pango.AttrScale(0.8f));
|
||||
|
||||
//
|
||||
// _socialBox
|
||||
//
|
||||
_socialBox = new Box(Orientation.Horizontal, 0)
|
||||
{
|
||||
Margin = 25,
|
||||
MarginBottom = 10
|
||||
};
|
||||
|
||||
//
|
||||
// _patreonEventBox
|
||||
//
|
||||
_patreonEventBox = new EventBox()
|
||||
{
|
||||
TooltipText = "Click to open the Ryujinx Patreon page in your default browser."
|
||||
};
|
||||
_patreonEventBox.ButtonPressEvent += PatreonButton_Pressed;
|
||||
|
||||
//
|
||||
// _patreonBox
|
||||
//
|
||||
_patreonBox = new Box(Orientation.Vertical, 0);
|
||||
|
||||
//
|
||||
// _patreonLogo
|
||||
//
|
||||
_patreonLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Patreon.png", 30, 30))
|
||||
{
|
||||
Margin = 10
|
||||
};
|
||||
|
||||
//
|
||||
// _patreonLabel
|
||||
//
|
||||
_patreonLabel = new Label("Patreon")
|
||||
{
|
||||
Justify = Justification.Center
|
||||
};
|
||||
|
||||
//
|
||||
// _githubEventBox
|
||||
//
|
||||
_githubEventBox = new EventBox()
|
||||
{
|
||||
TooltipText = "Click to open the Ryujinx GitHub page in your default browser."
|
||||
};
|
||||
_githubEventBox.ButtonPressEvent += GitHubButton_Pressed;
|
||||
|
||||
//
|
||||
// _githubBox
|
||||
//
|
||||
_githubBox = new Box(Orientation.Vertical, 0);
|
||||
|
||||
//
|
||||
// _githubLogo
|
||||
//
|
||||
_githubLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_GitHub.png", 30, 30))
|
||||
{
|
||||
Margin = 10
|
||||
};
|
||||
|
||||
//
|
||||
// _githubLabel
|
||||
//
|
||||
_githubLabel = new Label("GitHub")
|
||||
{
|
||||
Justify = Justification.Center
|
||||
};
|
||||
|
||||
//
|
||||
// _discordBox
|
||||
//
|
||||
_discordBox = new Box(Orientation.Vertical, 0);
|
||||
|
||||
//
|
||||
// _discordEventBox
|
||||
//
|
||||
_discordEventBox = new EventBox()
|
||||
{
|
||||
TooltipText = "Click to open an invite to the Ryujinx Discord server in your default browser."
|
||||
};
|
||||
_discordEventBox.ButtonPressEvent += DiscordButton_Pressed;
|
||||
|
||||
//
|
||||
// _discordLogo
|
||||
//
|
||||
_discordLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Discord.png", 30, 30))
|
||||
{
|
||||
Margin = 10
|
||||
};
|
||||
|
||||
//
|
||||
// _discordLabel
|
||||
//
|
||||
_discordLabel = new Label("Discord")
|
||||
{
|
||||
Justify = Justification.Center
|
||||
};
|
||||
|
||||
//
|
||||
// _twitterEventBox
|
||||
//
|
||||
_twitterEventBox = new EventBox()
|
||||
{
|
||||
TooltipText = "Click to open the Ryujinx Twitter page in your default browser."
|
||||
};
|
||||
_twitterEventBox.ButtonPressEvent += TwitterButton_Pressed;
|
||||
|
||||
//
|
||||
// _twitterBox
|
||||
//
|
||||
_twitterBox = new Box(Orientation.Vertical, 0);
|
||||
|
||||
//
|
||||
// _twitterLogo
|
||||
//
|
||||
_twitterLogo = new Image(new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Twitter.png", 30, 30))
|
||||
{
|
||||
Margin = 10
|
||||
};
|
||||
|
||||
//
|
||||
// _twitterLabel
|
||||
//
|
||||
_twitterLabel = new Label("Twitter")
|
||||
{
|
||||
Justify = Justification.Center
|
||||
};
|
||||
|
||||
//
|
||||
// _separator
|
||||
//
|
||||
_separator = new Separator(Orientation.Vertical)
|
||||
{
|
||||
Margin = 15
|
||||
};
|
||||
|
||||
//
|
||||
// _rightBox
|
||||
//
|
||||
_rightBox = new Box(Orientation.Vertical, 0)
|
||||
{
|
||||
Margin = 15,
|
||||
MarginTop = 40
|
||||
};
|
||||
|
||||
//
|
||||
// _aboutLabel
|
||||
//
|
||||
_aboutLabel = new Label("About :")
|
||||
{
|
||||
Halign = Align.Start,
|
||||
Attributes = new AttrList()
|
||||
};
|
||||
_aboutLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold));
|
||||
_aboutLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
|
||||
|
||||
//
|
||||
// _aboutDescriptionLabel
|
||||
//
|
||||
_aboutDescriptionLabel = new Label("Ryujinx is an emulator for the Nintendo Switch™.\n" +
|
||||
"Please support us on Patreon.\n" +
|
||||
"Get all the latest news on our Twitter or Discord.\n" +
|
||||
"Developers interested in contributing can find out more on our GitHub or Discord.")
|
||||
{
|
||||
Margin = 15,
|
||||
Halign = Align.Start
|
||||
};
|
||||
|
||||
//
|
||||
// _createdByLabel
|
||||
//
|
||||
_createdByLabel = new Label("Maintained by :")
|
||||
{
|
||||
Halign = Align.Start,
|
||||
Attributes = new AttrList()
|
||||
};
|
||||
_createdByLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold));
|
||||
_createdByLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
|
||||
|
||||
//
|
||||
// _createdByText
|
||||
//
|
||||
_createdByText = new TextView()
|
||||
{
|
||||
WrapMode = Gtk.WrapMode.Word,
|
||||
Editable = false,
|
||||
CursorVisible = false,
|
||||
Margin = 15,
|
||||
MarginRight = 30
|
||||
};
|
||||
_createdByText.Buffer.Text = "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD« and more...";
|
||||
|
||||
//
|
||||
// _contributorsEventBox
|
||||
//
|
||||
_contributorsEventBox = new EventBox();
|
||||
_contributorsEventBox.ButtonPressEvent += ContributorsButton_Pressed;
|
||||
|
||||
//
|
||||
// _contributorsLinkLabel
|
||||
//
|
||||
_contributorsLinkLabel = new Label("See All Contributors...")
|
||||
{
|
||||
TooltipText = "Click to open the Contributors page in your default browser.",
|
||||
MarginRight = 30,
|
||||
Halign = Align.End,
|
||||
Attributes = new AttrList()
|
||||
};
|
||||
_contributorsLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
|
||||
|
||||
//
|
||||
// _patreonNamesLabel
|
||||
//
|
||||
_patreonNamesLabel = new Label("Supported on Patreon by :")
|
||||
{
|
||||
Halign = Align.Start,
|
||||
Attributes = new AttrList()
|
||||
};
|
||||
_patreonNamesLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold));
|
||||
_patreonNamesLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single));
|
||||
|
||||
//
|
||||
// _patreonNamesScrolled
|
||||
//
|
||||
_patreonNamesScrolled = new ScrolledWindow()
|
||||
{
|
||||
Margin = 15,
|
||||
MarginRight = 30,
|
||||
Expand = true,
|
||||
ShadowType = ShadowType.In
|
||||
};
|
||||
_patreonNamesScrolled.SetPolicy(PolicyType.Never, PolicyType.Automatic);
|
||||
|
||||
//
|
||||
// _patreonNamesText
|
||||
//
|
||||
_patreonNamesText = new TextView()
|
||||
{
|
||||
WrapMode = Gtk.WrapMode.Word
|
||||
};
|
||||
_patreonNamesText.Buffer.Text = "Loading...";
|
||||
_patreonNamesText.SetProperty("editable", new GLib.Value(false));
|
||||
|
||||
#pragma warning restore CS0612
|
||||
|
||||
ShowComponent();
|
||||
}
|
||||
|
||||
private void ShowComponent()
|
||||
{
|
||||
_logoBox.Add(_ryujinxLogo);
|
||||
|
||||
_ryujinxLink.Add(_ryujinxLinkLabel);
|
||||
|
||||
_logoTextBox.Add(_ryujinxLabel);
|
||||
_logoTextBox.Add(_ryujinxPhoneticLabel);
|
||||
_logoTextBox.Add(_ryujinxLink);
|
||||
|
||||
_logoBox.Add(_logoTextBox);
|
||||
|
||||
_patreonBox.Add(_patreonLogo);
|
||||
_patreonBox.Add(_patreonLabel);
|
||||
_patreonEventBox.Add(_patreonBox);
|
||||
|
||||
_githubBox.Add(_githubLogo);
|
||||
_githubBox.Add(_githubLabel);
|
||||
_githubEventBox.Add(_githubBox);
|
||||
|
||||
_discordBox.Add(_discordLogo);
|
||||
_discordBox.Add(_discordLabel);
|
||||
_discordEventBox.Add(_discordBox);
|
||||
|
||||
_twitterBox.Add(_twitterLogo);
|
||||
_twitterBox.Add(_twitterLabel);
|
||||
_twitterEventBox.Add(_twitterBox);
|
||||
|
||||
_socialBox.Add(_patreonEventBox);
|
||||
_socialBox.Add(_githubEventBox);
|
||||
_socialBox.Add(_discordEventBox);
|
||||
_socialBox.Add(_twitterEventBox);
|
||||
|
||||
_leftBox.Add(_logoBox);
|
||||
_leftBox.Add(_versionLabel);
|
||||
_leftBox.Add(_disclaimerLabel);
|
||||
_leftBox.Add(_socialBox);
|
||||
|
||||
_contributorsEventBox.Add(_contributorsLinkLabel);
|
||||
_patreonNamesScrolled.Add(_patreonNamesText);
|
||||
|
||||
_rightBox.Add(_aboutLabel);
|
||||
_rightBox.Add(_aboutDescriptionLabel);
|
||||
_rightBox.Add(_createdByLabel);
|
||||
_rightBox.Add(_createdByText);
|
||||
_rightBox.Add(_contributorsEventBox);
|
||||
_rightBox.Add(_patreonNamesLabel);
|
||||
_rightBox.Add(_patreonNamesScrolled);
|
||||
|
||||
_mainBox.Add(_leftBox);
|
||||
_mainBox.Add(_separator);
|
||||
_mainBox.Add(_rightBox);
|
||||
|
||||
Add(_mainBox);
|
||||
|
||||
ShowAll();
|
||||
}
|
||||
}
|
||||
}
|
73
Ryujinx/Ui/Windows/AboutWindow.cs
Normal file
73
Ryujinx/Ui/Windows/AboutWindow.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
using Gtk;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Ui.Helper;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ui.Windows
|
||||
{
|
||||
public partial class AboutWindow : Window
|
||||
{
|
||||
public AboutWindow() : base($"Ryujinx {Program.Version} - About")
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_ = DownloadPatronsJson();
|
||||
}
|
||||
|
||||
private async Task DownloadPatronsJson()
|
||||
{
|
||||
if (!NetworkInterface.GetIsNetworkAvailable())
|
||||
{
|
||||
_patreonNamesText.Buffer.Text = "Connection Error.";
|
||||
}
|
||||
|
||||
HttpClient httpClient = new HttpClient();
|
||||
|
||||
try
|
||||
{
|
||||
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
|
||||
|
||||
_patreonNamesText.Buffer.Text = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString));
|
||||
}
|
||||
catch
|
||||
{
|
||||
_patreonNamesText.Buffer.Text = "API Error.";
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Events
|
||||
//
|
||||
private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenHelper.OpenUrl("https://ryujinx.org");
|
||||
}
|
||||
|
||||
private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenHelper.OpenUrl("https://www.patreon.com/ryujinx");
|
||||
}
|
||||
|
||||
private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx");
|
||||
}
|
||||
|
||||
private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenHelper.OpenUrl("https://discordapp.com/invite/N2FmfVc");
|
||||
}
|
||||
|
||||
private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenHelper.OpenUrl("https://twitter.com/RyujinxEmu");
|
||||
}
|
||||
|
||||
private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args)
|
||||
{
|
||||
OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a");
|
||||
}
|
||||
}
|
||||
}
|
1027
Ryujinx/Ui/Windows/ControllerWindow.cs
Normal file
1027
Ryujinx/Ui/Windows/ControllerWindow.cs
Normal file
File diff suppressed because it is too large
Load diff
2038
Ryujinx/Ui/Windows/ControllerWindow.glade
Normal file
2038
Ryujinx/Ui/Windows/ControllerWindow.glade
Normal file
File diff suppressed because it is too large
Load diff
253
Ryujinx/Ui/Windows/DlcWindow.cs
Normal file
253
Ryujinx/Ui/Windows/DlcWindow.cs
Normal file
|
@ -0,0 +1,253 @@
|
|||
using Gtk;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Ui.Widgets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
|
||||
|
||||
namespace Ryujinx.Ui.Windows
|
||||
{
|
||||
public class DlcWindow : Window
|
||||
{
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly string _titleId;
|
||||
private readonly string _dlcJsonPath;
|
||||
private readonly List<DlcContainer> _dlcContainerList;
|
||||
|
||||
#pragma warning disable CS0649, IDE0044
|
||||
[GUI] Label _baseTitleInfoLabel;
|
||||
[GUI] TreeView _dlcTreeView;
|
||||
[GUI] TreeSelection _dlcTreeSelection;
|
||||
#pragma warning restore CS0649, IDE0044
|
||||
|
||||
public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
|
||||
|
||||
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_dlcWindow").Handle)
|
||||
{
|
||||
builder.Autoconnect(this);
|
||||
|
||||
_titleId = titleId;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json");
|
||||
_baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]";
|
||||
|
||||
try
|
||||
{
|
||||
_dlcContainerList = JsonHelper.DeserializeFromFile<List<DlcContainer>>(_dlcJsonPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_dlcContainerList = new List<DlcContainer>();
|
||||
}
|
||||
|
||||
_dlcTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string));
|
||||
|
||||
CellRendererToggle enableToggle = new CellRendererToggle();
|
||||
enableToggle.Toggled += (sender, args) =>
|
||||
{
|
||||
_dlcTreeView.Model.GetIter(out TreeIter treeIter, new TreePath(args.Path));
|
||||
bool newValue = !(bool)_dlcTreeView.Model.GetValue(treeIter, 0);
|
||||
_dlcTreeView.Model.SetValue(treeIter, 0, newValue);
|
||||
|
||||
if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, treeIter))
|
||||
{
|
||||
do
|
||||
{
|
||||
_dlcTreeView.Model.SetValue(childIter, 0, newValue);
|
||||
}
|
||||
while (_dlcTreeView.Model.IterNext(ref childIter));
|
||||
}
|
||||
};
|
||||
|
||||
_dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0);
|
||||
_dlcTreeView.AppendColumn("TitleId", new CellRendererText(), "text", 1);
|
||||
_dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2);
|
||||
|
||||
foreach (DlcContainer dlcContainer in _dlcContainerList)
|
||||
{
|
||||
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", dlcContainer.Path);
|
||||
|
||||
using FileStream containerFile = File.OpenRead(dlcContainer.Path);
|
||||
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
|
||||
_virtualFileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DlcNca dlcNca in dlcContainer.DlcNcaList)
|
||||
{
|
||||
pfs.OpenFile(out IFile ncaFile, dlcNca.Path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
Nca nca = TryCreateNca(ncaFile.AsStorage(), dlcContainer.Path);
|
||||
|
||||
if (nca != null)
|
||||
{
|
||||
((TreeStore)_dlcTreeView.Model).AppendValues(parentIter, dlcNca.Enabled, nca.Header.TitleId.ToString("X16"), dlcNca.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {containerPath}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void AddButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
FileChooserDialog fileChooser = new FileChooserDialog("Select DLC files", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept)
|
||||
{
|
||||
SelectMultiple = true,
|
||||
Filter = new FileFilter()
|
||||
};
|
||||
fileChooser.SetPosition(WindowPosition.Center);
|
||||
fileChooser.Filter.AddPattern("*.nsp");
|
||||
|
||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||
{
|
||||
foreach (string containerPath in fileChooser.Filenames)
|
||||
{
|
||||
if (!File.Exists(containerPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (FileStream containerFile = File.OpenRead(containerPath))
|
||||
{
|
||||
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
|
||||
bool containsDlc = false;
|
||||
|
||||
_virtualFileSystem.ImportTickets(pfs);
|
||||
|
||||
TreeIter? parentIter = null;
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = TryCreateNca(ncaFile.AsStorage(), containerPath);
|
||||
|
||||
if (nca == null) continue;
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||
{
|
||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != _titleId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath);
|
||||
|
||||
((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
|
||||
containsDlc = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containsDlc)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("The specified file does not contain a DLC for the selected title!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileChooser.Dispose();
|
||||
}
|
||||
|
||||
private void RemoveButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
if (_dlcTreeSelection.GetSelected(out ITreeModel treeModel, out TreeIter treeIter))
|
||||
{
|
||||
if (_dlcTreeView.Model.IterParent(out TreeIter parentIter, treeIter) && _dlcTreeView.Model.IterNChildren(parentIter) <= 1)
|
||||
{
|
||||
((TreeStore)treeModel).Remove(ref parentIter);
|
||||
}
|
||||
else
|
||||
{
|
||||
((TreeStore)treeModel).Remove(ref treeIter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAllButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
List<TreeIter> toRemove = new List<TreeIter>();
|
||||
|
||||
if (_dlcTreeView.Model.GetIterFirst(out TreeIter iter))
|
||||
{
|
||||
do
|
||||
{
|
||||
toRemove.Add(iter);
|
||||
}
|
||||
while (_dlcTreeView.Model.IterNext(ref iter));
|
||||
}
|
||||
|
||||
foreach (TreeIter i in toRemove)
|
||||
{
|
||||
TreeIter j = i;
|
||||
((TreeStore)_dlcTreeView.Model).Remove(ref j);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
_dlcContainerList.Clear();
|
||||
|
||||
if (_dlcTreeView.Model.GetIterFirst(out TreeIter parentIter))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, parentIter))
|
||||
{
|
||||
DlcContainer dlcContainer = new DlcContainer
|
||||
{
|
||||
Path = (string)_dlcTreeView.Model.GetValue(parentIter, 2),
|
||||
DlcNcaList = new List<DlcNca>()
|
||||
};
|
||||
|
||||
do
|
||||
{
|
||||
dlcContainer.DlcNcaList.Add(new DlcNca
|
||||
{
|
||||
Enabled = (bool)_dlcTreeView.Model.GetValue(childIter, 0),
|
||||
TitleId = Convert.ToUInt64(_dlcTreeView.Model.GetValue(childIter, 1).ToString(), 16),
|
||||
Path = (string)_dlcTreeView.Model.GetValue(childIter, 2)
|
||||
});
|
||||
}
|
||||
while (_dlcTreeView.Model.IterNext(ref childIter));
|
||||
|
||||
_dlcContainerList.Add(dlcContainer);
|
||||
}
|
||||
}
|
||||
while (_dlcTreeView.Model.IterNext(ref parentIter));
|
||||
}
|
||||
|
||||
using (FileStream dlcJsonStream = File.Create(_dlcJsonPath, 4096, FileOptions.WriteThrough))
|
||||
{
|
||||
dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_dlcContainerList, true)));
|
||||
}
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CancelButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
202
Ryujinx/Ui/Windows/DlcWindow.glade
Normal file
202
Ryujinx/Ui/Windows/DlcWindow.glade
Normal file
|
@ -0,0 +1,202 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.36.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkWindow" id="_dlcWindow">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Ryujinx - DLC Manager</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center</property>
|
||||
<property name="default_width">550</property>
|
||||
<property name="default_height">350</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="MainBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="DlcBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="_baseTitleInfoLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="label" translatable="yes">Available DLC</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="_dlcTreeView">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_clickable">False</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="_dlcTreeSelection"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="layout_style">start</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="_addUpdate">
|
||||
<property name="label" translatable="yes">Add</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Adds an update to this list</property>
|
||||
<property name="margin_left">10</property>
|
||||
<signal name="clicked" handler="AddButton_Clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="_removeUpdate">
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Removes the selected update</property>
|
||||
<property name="margin_left">10</property>
|
||||
<signal name="clicked" handler="RemoveButton_Clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="_removeAllButton">
|
||||
<property name="label" translatable="yes">Remove All</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Removes the selected update</property>
|
||||
<property name="margin_left">10</property>
|
||||
<signal name="clicked" handler="RemoveAllButton_Clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="_saveButton">
|
||||
<property name="label" translatable="yes">Save</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<signal name="clicked" handler="SaveButton_Clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="_cancelButton">
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<signal name="clicked" handler="CancelButton_Clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
575
Ryujinx/Ui/Windows/SettingsWindow.cs
Normal file
575
Ryujinx/Ui/Windows/SettingsWindow.cs
Normal file
|
@ -0,0 +1,575 @@
|
|||
using Gtk;
|
||||
using Ryujinx.Audio;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Configuration;
|
||||
using Ryujinx.Configuration.System;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||
using Ryujinx.Ui.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.Ui.Windows
|
||||
{
|
||||
public class SettingsWindow : Window
|
||||
{
|
||||
private readonly MainWindow _parent;
|
||||
private readonly ListStore _gameDirsBoxStore;
|
||||
private readonly ListStore _audioBackendStore;
|
||||
private readonly TimeZoneContentManager _timeZoneContentManager;
|
||||
private readonly HashSet<string> _validTzRegions;
|
||||
|
||||
private long _systemTimeOffset;
|
||||
|
||||
#pragma warning disable CS0649, IDE0044
|
||||
[GUI] CheckButton _errorLogToggle;
|
||||
[GUI] CheckButton _warningLogToggle;
|
||||
[GUI] CheckButton _infoLogToggle;
|
||||
[GUI] CheckButton _stubLogToggle;
|
||||
[GUI] CheckButton _debugLogToggle;
|
||||
[GUI] CheckButton _fileLogToggle;
|
||||
[GUI] CheckButton _guestLogToggle;
|
||||
[GUI] CheckButton _fsAccessLogToggle;
|
||||
[GUI] Adjustment _fsLogSpinAdjustment;
|
||||
[GUI] ComboBoxText _graphicsDebugLevel;
|
||||
[GUI] CheckButton _dockedModeToggle;
|
||||
[GUI] CheckButton _discordToggle;
|
||||
[GUI] CheckButton _checkUpdatesToggle;
|
||||
[GUI] CheckButton _vSyncToggle;
|
||||
[GUI] CheckButton _shaderCacheToggle;
|
||||
[GUI] CheckButton _ptcToggle;
|
||||
[GUI] CheckButton _fsicToggle;
|
||||
[GUI] CheckButton _ignoreToggle;
|
||||
[GUI] CheckButton _directKeyboardAccess;
|
||||
[GUI] ComboBoxText _systemLanguageSelect;
|
||||
[GUI] ComboBoxText _systemRegionSelect;
|
||||
[GUI] Entry _systemTimeZoneEntry;
|
||||
[GUI] EntryCompletion _systemTimeZoneCompletion;
|
||||
[GUI] Box _audioBackendBox;
|
||||
[GUI] ComboBox _audioBackendSelect;
|
||||
[GUI] SpinButton _systemTimeYearSpin;
|
||||
[GUI] SpinButton _systemTimeMonthSpin;
|
||||
[GUI] SpinButton _systemTimeDaySpin;
|
||||
[GUI] SpinButton _systemTimeHourSpin;
|
||||
[GUI] SpinButton _systemTimeMinuteSpin;
|
||||
[GUI] Adjustment _systemTimeYearSpinAdjustment;
|
||||
[GUI] Adjustment _systemTimeMonthSpinAdjustment;
|
||||
[GUI] Adjustment _systemTimeDaySpinAdjustment;
|
||||
[GUI] Adjustment _systemTimeHourSpinAdjustment;
|
||||
[GUI] Adjustment _systemTimeMinuteSpinAdjustment;
|
||||
[GUI] CheckButton _custThemeToggle;
|
||||
[GUI] Entry _custThemePath;
|
||||
[GUI] ToggleButton _browseThemePath;
|
||||
[GUI] Label _custThemePathLabel;
|
||||
[GUI] TreeView _gameDirsBox;
|
||||
[GUI] Entry _addGameDirBox;
|
||||
[GUI] Entry _graphicsShadersDumpPath;
|
||||
[GUI] ComboBoxText _anisotropy;
|
||||
[GUI] ComboBoxText _aspectRatio;
|
||||
[GUI] ComboBoxText _resScaleCombo;
|
||||
[GUI] Entry _resScaleText;
|
||||
[GUI] ToggleButton _configureController1;
|
||||
[GUI] ToggleButton _configureController2;
|
||||
[GUI] ToggleButton _configureController3;
|
||||
[GUI] ToggleButton _configureController4;
|
||||
[GUI] ToggleButton _configureController5;
|
||||
[GUI] ToggleButton _configureController6;
|
||||
[GUI] ToggleButton _configureController7;
|
||||
[GUI] ToggleButton _configureController8;
|
||||
[GUI] ToggleButton _configureControllerH;
|
||||
|
||||
#pragma warning restore CS0649, IDE0044
|
||||
|
||||
public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : this(parent, new Builder("Ryujinx.Ui.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
|
||||
|
||||
private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : base(builder.GetObject("_settingsWin").Handle)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
builder.Autoconnect(this);
|
||||
|
||||
_timeZoneContentManager = new TimeZoneContentManager();
|
||||
_timeZoneContentManager.InitializeInstance(virtualFileSystem, contentManager, LibHac.FsSystem.IntegrityCheckLevel.None);
|
||||
|
||||
_validTzRegions = new HashSet<string>(_timeZoneContentManager.LocationNameCache.Length, StringComparer.Ordinal); // Zone regions are identifiers. Must match exactly.
|
||||
|
||||
// Bind Events.
|
||||
_configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player1);
|
||||
_configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player2);
|
||||
_configureController3.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player3);
|
||||
_configureController4.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player4);
|
||||
_configureController5.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player5);
|
||||
_configureController6.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player6);
|
||||
_configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player7);
|
||||
_configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player8);
|
||||
_configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Handheld);
|
||||
_systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut;
|
||||
|
||||
_resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
|
||||
|
||||
// Setup Currents.
|
||||
if (ConfigurationState.Instance.Logger.EnableFileLog)
|
||||
{
|
||||
_fileLogToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Logger.EnableError)
|
||||
{
|
||||
_errorLogToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Logger.EnableWarn)
|
||||
{
|
||||
_warningLogToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Logger.EnableInfo)
|
||||
{
|
||||
_infoLogToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Logger.EnableStub)
|
||||
{
|
||||
_stubLogToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Logger.EnableDebug)
|
||||
{
|
||||
_debugLogToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Logger.EnableGuest)
|
||||
{
|
||||
_guestLogToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Logger.EnableFsAccessLog)
|
||||
{
|
||||
_fsAccessLogToggle.Click();
|
||||
}
|
||||
|
||||
foreach (GraphicsDebugLevel level in Enum.GetValues(typeof(GraphicsDebugLevel)))
|
||||
{
|
||||
_graphicsDebugLevel.Append(level.ToString(), level.ToString());
|
||||
}
|
||||
|
||||
_graphicsDebugLevel.SetActiveId(ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value.ToString());
|
||||
|
||||
if (ConfigurationState.Instance.System.EnableDockedMode)
|
||||
{
|
||||
_dockedModeToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.EnableDiscordIntegration)
|
||||
{
|
||||
_discordToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.CheckUpdatesOnStart)
|
||||
{
|
||||
_checkUpdatesToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Graphics.EnableVsync)
|
||||
{
|
||||
_vSyncToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Graphics.EnableShaderCache)
|
||||
{
|
||||
_shaderCacheToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.System.EnablePtc)
|
||||
{
|
||||
_ptcToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.System.EnableFsIntegrityChecks)
|
||||
{
|
||||
_fsicToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.System.IgnoreMissingServices)
|
||||
{
|
||||
_ignoreToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Hid.EnableKeyboard)
|
||||
{
|
||||
_directKeyboardAccess.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Ui.EnableCustomTheme)
|
||||
{
|
||||
_custThemeToggle.Click();
|
||||
}
|
||||
|
||||
// Custom EntryCompletion Columns. If added to glade, need to override more signals
|
||||
ListStore tzList = new ListStore(typeof(string), typeof(string), typeof(string));
|
||||
_systemTimeZoneCompletion.Model = tzList;
|
||||
|
||||
CellRendererText offsetCol = new CellRendererText();
|
||||
CellRendererText abbrevCol = new CellRendererText();
|
||||
|
||||
_systemTimeZoneCompletion.PackStart(offsetCol, false);
|
||||
_systemTimeZoneCompletion.AddAttribute(offsetCol, "text", 0);
|
||||
_systemTimeZoneCompletion.TextColumn = 1; // Regions Column
|
||||
_systemTimeZoneCompletion.PackStart(abbrevCol, false);
|
||||
_systemTimeZoneCompletion.AddAttribute(abbrevCol, "text", 2);
|
||||
|
||||
int maxLocationLength = 0;
|
||||
|
||||
foreach (var (offset, location, abbr) in _timeZoneContentManager.ParseTzOffsets())
|
||||
{
|
||||
var hours = Math.DivRem(offset, 3600, out int seconds);
|
||||
var minutes = Math.Abs(seconds) / 60;
|
||||
|
||||
var abbr2 = (abbr.StartsWith('+') || abbr.StartsWith('-')) ? string.Empty : abbr;
|
||||
|
||||
tzList.AppendValues($"UTC{hours:+0#;-0#;+00}:{minutes:D2} ", location, abbr2);
|
||||
_validTzRegions.Add(location);
|
||||
|
||||
maxLocationLength = Math.Max(maxLocationLength, location.Length);
|
||||
}
|
||||
|
||||
_systemTimeZoneEntry.WidthChars = Math.Max(20, maxLocationLength + 1); // Ensure minimum Entry width
|
||||
_systemTimeZoneEntry.Text = _timeZoneContentManager.SanityCheckDeviceLocationName();
|
||||
|
||||
_systemTimeZoneCompletion.MatchFunc = TimeZoneMatchFunc;
|
||||
|
||||
_systemLanguageSelect.SetActiveId(ConfigurationState.Instance.System.Language.Value.ToString());
|
||||
_systemRegionSelect.SetActiveId(ConfigurationState.Instance.System.Region.Value.ToString());
|
||||
_resScaleCombo.SetActiveId(ConfigurationState.Instance.Graphics.ResScale.Value.ToString());
|
||||
_anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString());
|
||||
_aspectRatio.SetActiveId(((int)ConfigurationState.Instance.Graphics.AspectRatio.Value).ToString());
|
||||
|
||||
_custThemePath.Buffer.Text = ConfigurationState.Instance.Ui.CustomThemePath;
|
||||
_resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString();
|
||||
_resScaleText.Visible = _resScaleCombo.ActiveId == "-1";
|
||||
_graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||
_fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
|
||||
_systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset;
|
||||
|
||||
_gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0);
|
||||
_gameDirsBoxStore = new ListStore(typeof(string));
|
||||
_gameDirsBox.Model = _gameDirsBoxStore;
|
||||
|
||||
foreach (string gameDir in ConfigurationState.Instance.Ui.GameDirs.Value)
|
||||
{
|
||||
_gameDirsBoxStore.AppendValues(gameDir);
|
||||
}
|
||||
|
||||
if (_custThemeToggle.Active == false)
|
||||
{
|
||||
_custThemePath.Sensitive = false;
|
||||
_custThemePathLabel.Sensitive = false;
|
||||
_browseThemePath.Sensitive = false;
|
||||
}
|
||||
|
||||
//Setup system time spinners
|
||||
UpdateSystemTimeSpinners();
|
||||
|
||||
_audioBackendStore = new ListStore(typeof(string), typeof(AudioBackend));
|
||||
|
||||
TreeIter openAlIter = _audioBackendStore.AppendValues("OpenAL", AudioBackend.OpenAl);
|
||||
TreeIter soundIoIter = _audioBackendStore.AppendValues("SoundIO", AudioBackend.SoundIo);
|
||||
TreeIter dummyIter = _audioBackendStore.AppendValues("Dummy", AudioBackend.Dummy);
|
||||
|
||||
_audioBackendSelect = ComboBox.NewWithModelAndEntry(_audioBackendStore);
|
||||
_audioBackendSelect.EntryTextColumn = 0;
|
||||
_audioBackendSelect.Entry.IsEditable = false;
|
||||
|
||||
switch (ConfigurationState.Instance.System.AudioBackend.Value)
|
||||
{
|
||||
case AudioBackend.OpenAl:
|
||||
_audioBackendSelect.SetActiveIter(openAlIter);
|
||||
break;
|
||||
case AudioBackend.SoundIo:
|
||||
_audioBackendSelect.SetActiveIter(soundIoIter);
|
||||
break;
|
||||
case AudioBackend.Dummy:
|
||||
_audioBackendSelect.SetActiveIter(dummyIter);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_audioBackendBox.Add(_audioBackendSelect);
|
||||
_audioBackendSelect.Show();
|
||||
|
||||
bool openAlIsSupported = false;
|
||||
bool soundIoIsSupported = false;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
openAlIsSupported = OpenALAudioOut.IsSupported;
|
||||
soundIoIsSupported = SoundIoAudioOut.IsSupported;
|
||||
});
|
||||
|
||||
// This function runs whenever the dropdown is opened
|
||||
_audioBackendSelect.SetCellDataFunc(_audioBackendSelect.Cells[0], (layout, cell, model, iter) =>
|
||||
{
|
||||
cell.Sensitive = ((AudioBackend)_audioBackendStore.GetValue(iter, 1)) switch
|
||||
{
|
||||
AudioBackend.OpenAl => openAlIsSupported,
|
||||
AudioBackend.SoundIo => soundIoIsSupported,
|
||||
AudioBackend.Dummy => true,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateSystemTimeSpinners()
|
||||
{
|
||||
//Bind system time events
|
||||
_systemTimeYearSpin.ValueChanged -= SystemTimeSpin_ValueChanged;
|
||||
_systemTimeMonthSpin.ValueChanged -= SystemTimeSpin_ValueChanged;
|
||||
_systemTimeDaySpin.ValueChanged -= SystemTimeSpin_ValueChanged;
|
||||
_systemTimeHourSpin.ValueChanged -= SystemTimeSpin_ValueChanged;
|
||||
_systemTimeMinuteSpin.ValueChanged -= SystemTimeSpin_ValueChanged;
|
||||
|
||||
//Apply actual system time + SystemTimeOffset to system time spin buttons
|
||||
DateTime systemTime = DateTime.Now.AddSeconds(_systemTimeOffset);
|
||||
|
||||
_systemTimeYearSpinAdjustment.Value = systemTime.Year;
|
||||
_systemTimeMonthSpinAdjustment.Value = systemTime.Month;
|
||||
_systemTimeDaySpinAdjustment.Value = systemTime.Day;
|
||||
_systemTimeHourSpinAdjustment.Value = systemTime.Hour;
|
||||
_systemTimeMinuteSpinAdjustment.Value = systemTime.Minute;
|
||||
|
||||
//Format spin buttons text to include leading zeros
|
||||
_systemTimeYearSpin.Text = systemTime.Year.ToString("0000");
|
||||
_systemTimeMonthSpin.Text = systemTime.Month.ToString("00");
|
||||
_systemTimeDaySpin.Text = systemTime.Day.ToString("00");
|
||||
_systemTimeHourSpin.Text = systemTime.Hour.ToString("00");
|
||||
_systemTimeMinuteSpin.Text = systemTime.Minute.ToString("00");
|
||||
|
||||
//Bind system time events
|
||||
_systemTimeYearSpin.ValueChanged += SystemTimeSpin_ValueChanged;
|
||||
_systemTimeMonthSpin.ValueChanged += SystemTimeSpin_ValueChanged;
|
||||
_systemTimeDaySpin.ValueChanged += SystemTimeSpin_ValueChanged;
|
||||
_systemTimeHourSpin.ValueChanged += SystemTimeSpin_ValueChanged;
|
||||
_systemTimeMinuteSpin.ValueChanged += SystemTimeSpin_ValueChanged;
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
List<string> gameDirs = new List<string>();
|
||||
|
||||
_gameDirsBoxStore.GetIterFirst(out TreeIter treeIter);
|
||||
for (int i = 0; i < _gameDirsBoxStore.IterNChildren(); i++)
|
||||
{
|
||||
gameDirs.Add((string)_gameDirsBoxStore.GetValue(treeIter, 0));
|
||||
|
||||
_gameDirsBoxStore.IterNext(ref treeIter);
|
||||
}
|
||||
|
||||
if (!float.TryParse(_resScaleText.Buffer.Text, out float resScaleCustom) || resScaleCustom <= 0.0f)
|
||||
{
|
||||
resScaleCustom = 1.0f;
|
||||
}
|
||||
|
||||
if (_validTzRegions.Contains(_systemTimeZoneEntry.Text))
|
||||
{
|
||||
ConfigurationState.Instance.System.TimeZone.Value = _systemTimeZoneEntry.Text;
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableStub.Value = _stubLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableDebug.Value = _debugLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableGuest.Value = _guestLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableFsAccessLog.Value = _fsAccessLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.EnableFileLog.Value = _fileLogToggle.Active;
|
||||
ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value = Enum.Parse<GraphicsDebugLevel>(_graphicsDebugLevel.ActiveId);
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Value = _dockedModeToggle.Active;
|
||||
ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active;
|
||||
ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active;
|
||||
ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active;
|
||||
ConfigurationState.Instance.Graphics.EnableShaderCache.Value = _shaderCacheToggle.Active;
|
||||
ConfigurationState.Instance.System.EnablePtc.Value = _ptcToggle.Active;
|
||||
ConfigurationState.Instance.System.EnableFsIntegrityChecks.Value = _fsicToggle.Active;
|
||||
ConfigurationState.Instance.System.IgnoreMissingServices.Value = _ignoreToggle.Active;
|
||||
ConfigurationState.Instance.Hid.EnableKeyboard.Value = _directKeyboardAccess.Active;
|
||||
ConfigurationState.Instance.Ui.EnableCustomTheme.Value = _custThemeToggle.Active;
|
||||
ConfigurationState.Instance.System.Language.Value = Enum.Parse<Language>(_systemLanguageSelect.ActiveId);
|
||||
ConfigurationState.Instance.System.Region.Value = Enum.Parse<Configuration.System.Region>(_systemRegionSelect.ActiveId);
|
||||
ConfigurationState.Instance.System.SystemTimeOffset.Value = _systemTimeOffset;
|
||||
ConfigurationState.Instance.Ui.CustomThemePath.Value = _custThemePath.Buffer.Text;
|
||||
ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text;
|
||||
ConfigurationState.Instance.Ui.GameDirs.Value = gameDirs;
|
||||
ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value;
|
||||
ConfigurationState.Instance.Graphics.MaxAnisotropy.Value = float.Parse(_anisotropy.ActiveId, CultureInfo.InvariantCulture);
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Value = Enum.Parse<AspectRatio>(_aspectRatio.ActiveId);
|
||||
ConfigurationState.Instance.Graphics.ResScale.Value = int.Parse(_resScaleCombo.ActiveId);
|
||||
ConfigurationState.Instance.Graphics.ResScaleCustom.Value = resScaleCustom;
|
||||
|
||||
if (_audioBackendSelect.GetActiveIter(out TreeIter activeIter))
|
||||
{
|
||||
ConfigurationState.Instance.System.AudioBackend.Value = (AudioBackend)_audioBackendStore.GetValue(activeIter, 1);
|
||||
}
|
||||
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
_parent.UpdateGraphicsConfig();
|
||||
ThemeHelper.ApplyTheme();
|
||||
}
|
||||
|
||||
//
|
||||
// Events
|
||||
//
|
||||
private void TimeZoneEntry_FocusOut(object sender, FocusOutEventArgs e)
|
||||
{
|
||||
if (!_validTzRegions.Contains(_systemTimeZoneEntry.Text))
|
||||
{
|
||||
_systemTimeZoneEntry.Text = _timeZoneContentManager.SanityCheckDeviceLocationName();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TimeZoneMatchFunc(EntryCompletion compl, string key, TreeIter iter)
|
||||
{
|
||||
key = key.Trim().Replace(' ', '_');
|
||||
|
||||
return ((string)compl.Model.GetValue(iter, 1)).Contains(key, StringComparison.OrdinalIgnoreCase) || // region
|
||||
((string)compl.Model.GetValue(iter, 2)).StartsWith(key, StringComparison.OrdinalIgnoreCase) || // abbr
|
||||
((string)compl.Model.GetValue(iter, 0))[3..].StartsWith(key); // offset
|
||||
}
|
||||
|
||||
private void SystemTimeSpin_ValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
int year = _systemTimeYearSpin.ValueAsInt;
|
||||
int month = _systemTimeMonthSpin.ValueAsInt;
|
||||
int day = _systemTimeDaySpin.ValueAsInt;
|
||||
int hour = _systemTimeHourSpin.ValueAsInt;
|
||||
int minute = _systemTimeMinuteSpin.ValueAsInt;
|
||||
|
||||
if (!DateTime.TryParse(year + "-" + month + "-" + day + " " + hour + ":" + minute, out DateTime newTime))
|
||||
{
|
||||
UpdateSystemTimeSpinners();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
newTime = newTime.AddSeconds(DateTime.Now.Second).AddMilliseconds(DateTime.Now.Millisecond);
|
||||
|
||||
long systemTimeOffset = (long)Math.Ceiling((newTime - DateTime.Now).TotalMinutes) * 60L;
|
||||
|
||||
if (_systemTimeOffset != systemTimeOffset)
|
||||
{
|
||||
_systemTimeOffset = systemTimeOffset;
|
||||
UpdateSystemTimeSpinners();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDir_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
if (Directory.Exists(_addGameDirBox.Buffer.Text))
|
||||
{
|
||||
_gameDirsBoxStore.AppendValues(_addGameDirBox.Buffer.Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
FileChooserDialog fileChooser = new FileChooserDialog("Choose the game directory to add to the list", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept)
|
||||
{
|
||||
SelectMultiple = true
|
||||
};
|
||||
|
||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||
{
|
||||
foreach (string directory in fileChooser.Filenames)
|
||||
{
|
||||
bool directoryAdded = false;
|
||||
|
||||
if (_gameDirsBoxStore.GetIterFirst(out TreeIter treeIter))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (directory.Equals((string)_gameDirsBoxStore.GetValue(treeIter, 0)))
|
||||
{
|
||||
directoryAdded = true;
|
||||
break;
|
||||
}
|
||||
} while(_gameDirsBoxStore.IterNext(ref treeIter));
|
||||
}
|
||||
|
||||
if (!directoryAdded)
|
||||
{
|
||||
_gameDirsBoxStore.AppendValues(directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileChooser.Dispose();
|
||||
}
|
||||
|
||||
_addGameDirBox.Buffer.Text = "";
|
||||
|
||||
((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
|
||||
}
|
||||
|
||||
private void RemoveDir_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
TreeSelection selection = _gameDirsBox.Selection;
|
||||
|
||||
if (selection.GetSelected(out TreeIter treeIter))
|
||||
{
|
||||
_gameDirsBoxStore.Remove(ref treeIter);
|
||||
}
|
||||
|
||||
((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
|
||||
}
|
||||
|
||||
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 sender, EventArgs args)
|
||||
{
|
||||
using (FileChooserDialog fileChooser = new FileChooserDialog("Choose the theme to load", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Select", ResponseType.Accept))
|
||||
{
|
||||
fileChooser.Filter = new FileFilter();
|
||||
fileChooser.Filter.AddPattern("*.css");
|
||||
|
||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||
{
|
||||
_custThemePath.Buffer.Text = fileChooser.Filename;
|
||||
}
|
||||
}
|
||||
|
||||
_browseThemePath.SetStateFlags(StateFlags.Normal, true);
|
||||
}
|
||||
|
||||
private void ConfigureController_Pressed(object sender, PlayerIndex playerIndex)
|
||||
{
|
||||
((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true);
|
||||
|
||||
new ControllerWindow(playerIndex).Show();
|
||||
}
|
||||
|
||||
private void SaveToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
SaveSettings();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void ApplyToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
private void CloseToggle_Activated(object sender, EventArgs args)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
2411
Ryujinx/Ui/Windows/SettingsWindow.glade
Normal file
2411
Ryujinx/Ui/Windows/SettingsWindow.glade
Normal file
File diff suppressed because it is too large
Load diff
202
Ryujinx/Ui/Windows/TitleUpdateWindow.cs
Normal file
202
Ryujinx/Ui/Windows/TitleUpdateWindow.cs
Normal file
|
@ -0,0 +1,202 @@
|
|||
using Gtk;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.FsSystem.NcaUtils;
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Ui.Widgets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
|
||||
|
||||
namespace Ryujinx.Ui.Windows
|
||||
{
|
||||
public class TitleUpdateWindow : Window
|
||||
{
|
||||
private readonly MainWindow _parent;
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly string _titleId;
|
||||
private readonly string _updateJsonPath;
|
||||
|
||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
||||
|
||||
private readonly Dictionary<RadioButton, string> _radioButtonToPathDictionary;
|
||||
|
||||
#pragma warning disable CS0649, IDE0044
|
||||
[GUI] Label _baseTitleInfoLabel;
|
||||
[GUI] Box _availableUpdatesBox;
|
||||
[GUI] RadioButton _noUpdateRadioButton;
|
||||
#pragma warning restore CS0649, IDE0044
|
||||
|
||||
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
|
||||
|
||||
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_titleUpdateWindow").Handle)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
builder.Autoconnect(this);
|
||||
|
||||
_titleId = titleId;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "updates.json");
|
||||
_radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
|
||||
|
||||
try
|
||||
{
|
||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_updateJsonPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||
{
|
||||
Selected = "",
|
||||
Paths = new List<string>()
|
||||
};
|
||||
}
|
||||
|
||||
_baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]";
|
||||
|
||||
foreach (string path in _titleUpdateWindowData.Paths)
|
||||
{
|
||||
AddUpdate(path);
|
||||
}
|
||||
|
||||
if (_titleUpdateWindowData.Selected == "")
|
||||
{
|
||||
_noUpdateRadioButton.Active = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ((RadioButton update, var _) in _radioButtonToPathDictionary.Where(keyValuePair => keyValuePair.Value == _titleUpdateWindowData.Selected))
|
||||
{
|
||||
update.Active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new ApplicationControlProperty();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(out IFile nacpFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
RadioButton radioButton = new RadioButton($"Version {controlData.DisplayVersion.ToString()} - {path}");
|
||||
radioButton.JoinGroup(_noUpdateRadioButton);
|
||||
|
||||
_availableUpdatesBox.Add(radioButton);
|
||||
_radioButtonToPathDictionary.Add(radioButton, path);
|
||||
|
||||
radioButton.Show();
|
||||
radioButton.Active = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveUpdates(bool removeSelectedOnly = false)
|
||||
{
|
||||
foreach (RadioButton radioButton in _noUpdateRadioButton.Group)
|
||||
{
|
||||
if (radioButton.Label != "No Update" && (!removeSelectedOnly || radioButton.Active))
|
||||
{
|
||||
_availableUpdatesBox.Remove(radioButton);
|
||||
_radioButtonToPathDictionary.Remove(radioButton);
|
||||
radioButton.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
using (FileChooserDialog fileChooser = new FileChooserDialog("Select update files", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept))
|
||||
{
|
||||
fileChooser.SelectMultiple = true;
|
||||
fileChooser.SetPosition(WindowPosition.Center);
|
||||
fileChooser.Filter = new FileFilter();
|
||||
fileChooser.Filter.AddPattern("*.nsp");
|
||||
|
||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||
{
|
||||
foreach (string path in fileChooser.Filenames)
|
||||
{
|
||||
AddUpdate(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
RemoveUpdates(true);
|
||||
}
|
||||
|
||||
private void RemoveAllButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
RemoveUpdates();
|
||||
}
|
||||
|
||||
private void SaveButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Clear();
|
||||
_titleUpdateWindowData.Selected = "";
|
||||
|
||||
foreach (string paths in _radioButtonToPathDictionary.Values)
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Add(paths);
|
||||
}
|
||||
|
||||
foreach (RadioButton radioButton in _noUpdateRadioButton.Group)
|
||||
{
|
||||
if (radioButton.Active)
|
||||
{
|
||||
_titleUpdateWindowData.Selected = _radioButtonToPathDictionary.TryGetValue(radioButton, out string updatePath) ? updatePath : "";
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream dlcJsonStream = File.Create(_updateJsonPath, 4096, FileOptions.WriteThrough))
|
||||
{
|
||||
dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
||||
}
|
||||
|
||||
_parent.UpdateGameTable();
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CancelButton_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
214
Ryujinx/Ui/Windows/TitleUpdateWindow.glade
Normal file
214
Ryujinx/Ui/Windows/TitleUpdateWindow.glade
Normal file
|
@ -0,0 +1,214 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.36.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkWindow" id="_titleUpdateWindow">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Ryujinx - Title Update Manager</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center</property>
|
||||
<property name="default_width">550</property>
|
||||
<property name="default_height">250</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="MainBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="UpdatesBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="_baseTitleInfoLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="label" translatable="yes">Available Updates</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_left">10</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="_availableUpdatesBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="_noUpdateRadioButton">
|
||||
<property name="label" translatable="yes">No Update</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkButtonBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="layout_style">start</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="_addUpdate">
|
||||
<property name="label" translatable="yes">Add</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Adds an update to this list</property>
|
||||
<property name="margin_left">10</property>
|
||||
<signal name="clicked" handler="AddButton_Clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="_removeUpdate">
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Removes the selected update</property>
|
||||
<property name="margin_left">10</property>
|
||||
<signal name="clicked" handler="RemoveButton_Clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="_removeAllButton">
|
||||
<property name="label" translatable="yes">Remove All</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Removes the selected update</property>
|
||||
<property name="margin_left">10</property>
|
||||
<signal name="clicked" handler="RemoveAllButton_Clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="_saveButton">
|
||||
<property name="label" translatable="yes">Save</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<signal name="clicked" handler="SaveButton_Clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="_cancelButton">
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="margin_right">10</property>
|
||||
<property name="margin_top">2</property>
|
||||
<property name="margin_bottom">2</property>
|
||||
<signal name="clicked" handler="CancelButton_Clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
Loading…
Add table
Add a link
Reference in a new issue