forked from Mirror/Ryujinx
Implement dlc management window (#1313)
* Implement dlc management window * reduce repetition * Implement per NCA toggling of DLC rather than per container
This commit is contained in:
parent
fcd187ce42
commit
2ed9db1fcd
10 changed files with 581 additions and 164 deletions
10
Ryujinx.Common/Configuration/DlcContainer.cs
Normal file
10
Ryujinx.Common/Configuration/DlcContainer.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
|
{
|
||||||
|
public struct DlcContainer
|
||||||
|
{
|
||||||
|
public string Path { get; set; }
|
||||||
|
public List<DlcNca> DlcNcaList { get; set; }
|
||||||
|
}
|
||||||
|
}
|
9
Ryujinx.Common/Configuration/DlcNca.cs
Normal file
9
Ryujinx.Common/Configuration/DlcNca.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
|
{
|
||||||
|
public struct DlcNca
|
||||||
|
{
|
||||||
|
public string Path { get; set; }
|
||||||
|
public ulong TitleId { get; set; }
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ using LibHac.Fs;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.FsSystem.NcaUtils;
|
using LibHac.FsSystem.NcaUtils;
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using LibHac.Spl;
|
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
using Ryujinx.HLE.HOS.Services.Time;
|
using Ryujinx.HLE.HOS.Services.Time;
|
||||||
|
@ -241,6 +240,18 @@ namespace Ryujinx.HLE.FileSystem.Content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool enabled)
|
||||||
|
{
|
||||||
|
if (!_aocData.TryAdd(titleId, new AocItem(containerPath, ncaPath, enabled)))
|
||||||
|
{
|
||||||
|
Logger.PrintWarning(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {titleId:X16}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.PrintInfo(LogClass.Application, $"Found AddOnContent with TitleId {titleId:X16}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ClearAocData() => _aocData.Clear();
|
public void ClearAocData() => _aocData.Clear();
|
||||||
|
|
||||||
public int GetAocCount() => _aocData.Where(e => e.Value.Enabled).Count();
|
public int GetAocCount() => _aocData.Where(e => e.Value.Enabled).Count();
|
||||||
|
|
|
@ -149,17 +149,6 @@ namespace Ryujinx.HLE.HOS
|
||||||
_contentManager.ClearAocData();
|
_contentManager.ClearAocData();
|
||||||
_contentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId);
|
_contentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId);
|
||||||
|
|
||||||
// Check all nsp's in the base directory for AOC
|
|
||||||
foreach (var fn in new FileInfo(xciFile).Directory.EnumerateFiles("*.nsp"))
|
|
||||||
{
|
|
||||||
using (FileStream fs = fn.OpenRead())
|
|
||||||
using (IStorage storage = fs.AsStorage())
|
|
||||||
using (PartitionFileSystem pfs = new PartitionFileSystem(storage))
|
|
||||||
{
|
|
||||||
_contentManager.AddAocData(pfs, fn.FullName, mainNca.Header.TitleId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadNca(mainNca, patchNca, controlNca);
|
LoadNca(mainNca, patchNca, controlNca);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,18 +185,6 @@ namespace Ryujinx.HLE.HOS
|
||||||
_contentManager.ClearAocData();
|
_contentManager.ClearAocData();
|
||||||
_contentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId);
|
_contentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId);
|
||||||
|
|
||||||
// Check all nsp's in the base directory for AOC
|
|
||||||
foreach (var fn in new FileInfo(nspFile).Directory.EnumerateFiles("*.nsp"))
|
|
||||||
{
|
|
||||||
if (fn.FullName == nspFile) continue;
|
|
||||||
using (FileStream fs = fn.OpenRead())
|
|
||||||
using (IStorage storage = fs.AsStorage())
|
|
||||||
using (PartitionFileSystem pfs = new PartitionFileSystem(storage))
|
|
||||||
{
|
|
||||||
_contentManager.AddAocData(pfs, fn.FullName, mainNca.Header.TitleId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadNca(mainNca, patchNca, controlNca);
|
LoadNca(mainNca, patchNca, controlNca);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -238,7 +215,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
IStorage dataStorage = null;
|
IStorage dataStorage = null;
|
||||||
IFileSystem codeFs = null;
|
IFileSystem codeFs = null;
|
||||||
|
|
||||||
string titleUpdateMetadataPath = System.IO.Path.Combine(_fileSystem.GetBasePath(), "games", mainNca.Header.TitleId.ToString("x16"), "updates.json");
|
// Load Update
|
||||||
|
string titleUpdateMetadataPath = Path.Combine(_fileSystem.GetBasePath(), "games", mainNca.Header.TitleId.ToString("x16"), "updates.json");
|
||||||
|
|
||||||
if (File.Exists(titleUpdateMetadataPath))
|
if (File.Exists(titleUpdateMetadataPath))
|
||||||
{
|
{
|
||||||
|
@ -274,6 +252,22 @@ namespace Ryujinx.HLE.HOS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load Aoc
|
||||||
|
string titleAocMetadataPath = Path.Combine(_fileSystem.GetBasePath(), "games", mainNca.Header.TitleId.ToString("x16"), "dlc.json");
|
||||||
|
|
||||||
|
if (File.Exists(titleAocMetadataPath))
|
||||||
|
{
|
||||||
|
List<DlcContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DlcContainer>>(titleAocMetadataPath);
|
||||||
|
|
||||||
|
foreach (DlcContainer dlcContainer in dlcContainerList)
|
||||||
|
{
|
||||||
|
foreach (DlcNca dlcNca in dlcContainer.DlcNcaList)
|
||||||
|
{
|
||||||
|
_contentManager.AddAocItem(dlcNca.TitleId, dlcContainer.Path, dlcNca.Path, dlcNca.Enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (patchNca == null)
|
if (patchNca == null)
|
||||||
{
|
{
|
||||||
if (mainNca.CanOpenSection(NcaSectionType.Data))
|
if (mainNca.CanOpenSection(NcaSectionType.Data))
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
<None Remove="Ui\assets\Icon.png" />
|
<None Remove="Ui\assets\Icon.png" />
|
||||||
<None Remove="Ui\assets\TwitterLogo.png" />
|
<None Remove="Ui\assets\TwitterLogo.png" />
|
||||||
<None Remove="Ui\ControllerWindow.glade" />
|
<None Remove="Ui\ControllerWindow.glade" />
|
||||||
<None Remove="Ui\GameTableContextMenu.glade" />
|
<None Remove="Ui\DlcWindow.glade" />
|
||||||
<None Remove="Ui\MainWindow.glade" />
|
<None Remove="Ui\MainWindow.glade" />
|
||||||
<None Remove="Ui\ProfileDialog.glade" />
|
<None Remove="Ui\ProfileDialog.glade" />
|
||||||
<None Remove="Ui\SettingsWindow.glade" />
|
<None Remove="Ui\SettingsWindow.glade" />
|
||||||
|
@ -71,10 +71,10 @@
|
||||||
<EmbeddedResource Include="Ui\assets\Icon.png" />
|
<EmbeddedResource Include="Ui\assets\Icon.png" />
|
||||||
<EmbeddedResource Include="Ui\assets\TwitterLogo.png" />
|
<EmbeddedResource Include="Ui\assets\TwitterLogo.png" />
|
||||||
<EmbeddedResource Include="Ui\ControllerWindow.glade" />
|
<EmbeddedResource Include="Ui\ControllerWindow.glade" />
|
||||||
<EmbeddedResource Include="Ui\GameTableContextMenu.glade" />
|
|
||||||
<EmbeddedResource Include="Ui\MainWindow.glade" />
|
<EmbeddedResource Include="Ui\MainWindow.glade" />
|
||||||
<EmbeddedResource Include="Ui\ProfileDialog.glade" />
|
<EmbeddedResource Include="Ui\ProfileDialog.glade" />
|
||||||
<EmbeddedResource Include="Ui\SettingsWindow.glade" />
|
<EmbeddedResource Include="Ui\SettingsWindow.glade" />
|
||||||
|
<EmbeddedResource Include="Ui\DlcWindow.glade" />
|
||||||
<EmbeddedResource Include="Ui\TitleUpdateWindow.glade" />
|
<EmbeddedResource Include="Ui\TitleUpdateWindow.glade" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
244
Ryujinx/Ui/DlcWindow.cs
Normal file
244
Ryujinx/Ui/DlcWindow.cs
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
using Gtk;
|
||||||
|
using LibHac;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
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(string titleId, string titleName, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.DlcWindow.glade"), titleId, titleName, virtualFileSystem) { }
|
||||||
|
|
||||||
|
private DlcWindow(Builder builder, string titleId, string titleName, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_dlcWindow").Handle)
|
||||||
|
{
|
||||||
|
builder.Autoconnect(this);
|
||||||
|
|
||||||
|
_titleId = titleId;
|
||||||
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
_dlcJsonPath = System.IO.Path.Combine(virtualFileSystem.GetBasePath(), "games", _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 (InvalidDataException exception)
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Application, $"{exception.Message}. Errored File: {containerPath}");
|
||||||
|
|
||||||
|
GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add DLC Failed!", "The NCA header content type check has failed. This is usually because the header key is incorrect or missing.");
|
||||||
|
}
|
||||||
|
catch (MissingKeyException exception)
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {containerPath}");
|
||||||
|
|
||||||
|
GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add DLC Failed!", $"Your key set is missing a key with the name: {exception.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
186
Ryujinx/Ui/DlcWindow.glade
Normal file
186
Ryujinx/Ui/DlcWindow.glade
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.22.1 -->
|
||||||
|
<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>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -8,7 +8,6 @@ using LibHac.FsSystem;
|
||||||
using LibHac.FsSystem.NcaUtils;
|
using LibHac.FsSystem.NcaUtils;
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using LibHac.Spl;
|
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
|
@ -20,64 +19,97 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
using static LibHac.Fs.ApplicationSaveDataManagement;
|
using static LibHac.Fs.ApplicationSaveDataManagement;
|
||||||
using GUI = Gtk.Builder.ObjectAttribute;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ui
|
namespace Ryujinx.Ui
|
||||||
{
|
{
|
||||||
public class GameTableContextMenu : Menu
|
public class GameTableContextMenu : Menu
|
||||||
{
|
{
|
||||||
private ListStore _gameTableStore;
|
private readonly ListStore _gameTableStore;
|
||||||
private TreeIter _rowIter;
|
private readonly TreeIter _rowIter;
|
||||||
private VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
|
|
||||||
|
private readonly BlitStruct<ApplicationControlProperty> _controlData;
|
||||||
|
|
||||||
private MessageDialog _dialog;
|
private MessageDialog _dialog;
|
||||||
private bool _cancel;
|
private bool _cancel;
|
||||||
|
|
||||||
private BlitStruct<ApplicationControlProperty> _controlData;
|
|
||||||
|
|
||||||
#pragma warning disable CS0649
|
|
||||||
#pragma warning disable IDE0044
|
|
||||||
[GUI] MenuItem _openSaveUserDir;
|
|
||||||
[GUI] MenuItem _openSaveDeviceDir;
|
|
||||||
[GUI] MenuItem _openSaveBcatDir;
|
|
||||||
[GUI] MenuItem _manageTitleUpdates;
|
|
||||||
[GUI] MenuItem _extractRomFs;
|
|
||||||
[GUI] MenuItem _extractExeFs;
|
|
||||||
[GUI] MenuItem _extractLogo;
|
|
||||||
#pragma warning restore CS0649
|
|
||||||
#pragma warning restore IDE0044
|
|
||||||
|
|
||||||
public GameTableContextMenu(ListStore gameTableStore, BlitStruct<ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem)
|
public GameTableContextMenu(ListStore gameTableStore, BlitStruct<ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem)
|
||||||
: this(new Builder("Ryujinx.Ui.GameTableContextMenu.glade"), gameTableStore, controlData, rowIter, virtualFileSystem) { }
|
|
||||||
|
|
||||||
private GameTableContextMenu(Builder builder, ListStore gameTableStore, BlitStruct<ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_contextMenu").Handle)
|
|
||||||
{
|
{
|
||||||
builder.Autoconnect(this);
|
|
||||||
|
|
||||||
_gameTableStore = gameTableStore;
|
_gameTableStore = gameTableStore;
|
||||||
_rowIter = rowIter;
|
_rowIter = rowIter;
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_controlData = controlData;
|
_controlData = controlData;
|
||||||
|
|
||||||
_openSaveUserDir.Activated += OpenSaveUserDir_Clicked;
|
MenuItem openSaveUserDir = new MenuItem("Open User Save Directory")
|
||||||
_openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked;
|
{
|
||||||
_openSaveBcatDir.Activated += OpenSaveBcatDir_Clicked;
|
Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0,
|
||||||
_manageTitleUpdates.Activated += ManageTitleUpdates_Clicked;
|
TooltipText = "Open the folder where the User save for the application is loaded"
|
||||||
_extractRomFs.Activated += ExtractRomFs_Clicked;
|
};
|
||||||
_extractExeFs.Activated += ExtractExeFs_Clicked;
|
|
||||||
_extractLogo.Activated += ExtractLogo_Clicked;
|
|
||||||
|
|
||||||
_openSaveUserDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
|
MenuItem openSaveDeviceDir = new MenuItem("Open Device Save Directory")
|
||||||
_openSaveDeviceDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0;
|
{
|
||||||
_openSaveBcatDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0;
|
Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0,
|
||||||
|
TooltipText = "Open the folder where the Device save for the application is loaded"
|
||||||
|
};
|
||||||
|
|
||||||
|
MenuItem openSaveBcatDir = new MenuItem("Open BCAT Save Directory")
|
||||||
|
{
|
||||||
|
Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0,
|
||||||
|
TooltipText = "Open the folder where the BCAT save for the application is loaded"
|
||||||
|
};
|
||||||
|
|
||||||
|
MenuItem manageTitleUpdates = new MenuItem("Manage Title Updates")
|
||||||
|
{
|
||||||
|
TooltipText = "Open the title update management window"
|
||||||
|
};
|
||||||
|
|
||||||
|
MenuItem manageDlc = new MenuItem("Manage DLC")
|
||||||
|
{
|
||||||
|
TooltipText = "Open the DLC management window"
|
||||||
|
};
|
||||||
|
|
||||||
string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower();
|
string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower();
|
||||||
if (ext != ".nca" && ext != ".nsp" && ext != ".pfs0" && ext != ".xci")
|
bool hasNca = ext == ".nca" || ext == ".nsp" || ext == ".pfs0" || ext == ".xci";
|
||||||
|
|
||||||
|
MenuItem extractRomFs = new MenuItem("Extract RomFS Section")
|
||||||
{
|
{
|
||||||
_extractRomFs.Sensitive = false;
|
Sensitive = hasNca,
|
||||||
_extractExeFs.Sensitive = false;
|
TooltipText = "Exctact the RomFs section present in the main NCA"
|
||||||
_extractLogo.Sensitive = false;
|
};
|
||||||
}
|
|
||||||
|
MenuItem extractExeFs = new MenuItem("Extract ExeFS Section")
|
||||||
|
{
|
||||||
|
Sensitive = hasNca,
|
||||||
|
TooltipText = "Exctact the ExeFs section present in the main NCA"
|
||||||
|
};
|
||||||
|
|
||||||
|
MenuItem extractLogo = new MenuItem("Extract Logo Section")
|
||||||
|
{
|
||||||
|
Sensitive = hasNca,
|
||||||
|
TooltipText = "Exctact the Logo section present in the main NCA"
|
||||||
|
};
|
||||||
|
|
||||||
|
openSaveUserDir.Activated += OpenSaveUserDir_Clicked;
|
||||||
|
openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked;
|
||||||
|
openSaveBcatDir.Activated += OpenSaveBcatDir_Clicked;
|
||||||
|
manageTitleUpdates.Activated += ManageTitleUpdates_Clicked;
|
||||||
|
manageDlc.Activated += ManageDlc_Clicked;
|
||||||
|
extractRomFs.Activated += ExtractRomFs_Clicked;
|
||||||
|
extractExeFs.Activated += ExtractExeFs_Clicked;
|
||||||
|
extractLogo.Activated += ExtractLogo_Clicked;
|
||||||
|
|
||||||
|
this.Add(openSaveUserDir);
|
||||||
|
this.Add(openSaveDeviceDir);
|
||||||
|
this.Add(openSaveBcatDir);
|
||||||
|
this.Add(new SeparatorMenuItem());
|
||||||
|
this.Add(manageTitleUpdates);
|
||||||
|
this.Add(manageDlc);
|
||||||
|
this.Add(new SeparatorMenuItem());
|
||||||
|
this.Add(extractRomFs);
|
||||||
|
this.Add(extractExeFs);
|
||||||
|
this.Add(extractLogo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, SaveDataFilter filter, out ulong saveDataId)
|
private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, SaveDataFilter filter, out ulong saveDataId)
|
||||||
|
@ -478,7 +510,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
string saveDir = GetSaveDataDirectory(saveDataId);
|
string saveDir = GetSaveDataDirectory(saveDataId);
|
||||||
|
|
||||||
Process.Start(new ProcessStartInfo()
|
Process.Start(new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = saveDir,
|
FileName = saveDir,
|
||||||
UseShellExecute = true,
|
UseShellExecute = true,
|
||||||
|
@ -531,6 +563,15 @@ namespace Ryujinx.Ui
|
||||||
titleUpdateWindow.Show();
|
titleUpdateWindow.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ManageDlc_Clicked(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
|
||||||
|
string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
|
||||||
|
|
||||||
|
DlcWindow dlcWindow = new DlcWindow(titleId, titleName, _virtualFileSystem);
|
||||||
|
dlcWindow.Show();
|
||||||
|
}
|
||||||
|
|
||||||
private void ExtractRomFs_Clicked(object sender, EventArgs args)
|
private void ExtractRomFs_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
ExtractSection(NcaSectionType.Data);
|
ExtractSection(NcaSectionType.Data);
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Generated with glade 3.22.1 -->
|
|
||||||
<interface>
|
|
||||||
<requires lib="gtk+" version="3.20"/>
|
|
||||||
<object class="GtkMenu" id="_contextMenu">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="_openSaveUserDir">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Open the folder where the User save for the application is loaded</property>
|
|
||||||
<property name="label" translatable="yes">Open User Save Directory</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="_openSaveDeviceDir">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Open the folder where the Device save for the application is loaded</property>
|
|
||||||
<property name="label" translatable="yes">Open Device Save Directory</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="_openSaveBcatDir">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Open the folder where the BCAT save for the application is loaded</property>
|
|
||||||
<property name="label" translatable="yes">Open BCAT Save Directory</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSeparatorMenuItem">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="_manageTitleUpdates">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="label" translatable="yes">Manage Title Updates</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSeparatorMenuItem">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="_extractRomFs">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="label" translatable="yes">Extract RomFS Section</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="_extractExeFs">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="label" translatable="yes">Extract ExeFS Section</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="_extractLogo">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="label" translatable="yes">Extract Logo Section</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</interface>
|
|
|
@ -5,13 +5,13 @@ using LibHac.Fs;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.FsSystem.NcaUtils;
|
using LibHac.FsSystem.NcaUtils;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using LibHac.Spl;
|
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
using GUI = Gtk.Builder.ObjectAttribute;
|
using GUI = Gtk.Builder.ObjectAttribute;
|
||||||
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
|
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
|
||||||
|
@ -20,11 +20,12 @@ namespace Ryujinx.Ui
|
||||||
{
|
{
|
||||||
public class TitleUpdateWindow : Window
|
public class TitleUpdateWindow : Window
|
||||||
{
|
{
|
||||||
private readonly string _titleId;
|
|
||||||
private readonly VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
|
private readonly string _titleId;
|
||||||
|
private readonly string _updateJsonPath;
|
||||||
|
|
||||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
private TitleUpdateMetadata _titleUpdateWindowData;
|
||||||
private Dictionary<RadioButton, string> _radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
|
private Dictionary<RadioButton, string> _radioButtonToPathDictionary;
|
||||||
|
|
||||||
#pragma warning disable CS0649, IDE0044
|
#pragma warning disable CS0649, IDE0044
|
||||||
[GUI] Label _baseTitleInfoLabel;
|
[GUI] Label _baseTitleInfoLabel;
|
||||||
|
@ -40,12 +41,12 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
_titleId = titleId;
|
_titleId = titleId;
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
_updateJsonPath = System.IO.Path.Combine(_virtualFileSystem.GetBasePath(), "games", _titleId, "updates.json");
|
||||||
|
_radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string path = System.IO.Path.Combine(_virtualFileSystem.GetBasePath(), "games", _titleId, "updates.json");
|
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_updateJsonPath);
|
||||||
|
|
||||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(path);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -56,7 +57,7 @@ namespace Ryujinx.Ui
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId}]";
|
_baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]";
|
||||||
|
|
||||||
foreach (string path in _titleUpdateWindowData.Paths)
|
foreach (string path in _titleUpdateWindowData.Paths)
|
||||||
{
|
{
|
||||||
|
@ -194,9 +195,10 @@ namespace Ryujinx.Ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string path = System.IO.Path.Combine(_virtualFileSystem.GetBasePath(), "games", _titleId, "updates.json");
|
using (FileStream dlcJsonStream = File.Create(_updateJsonPath, 4096, FileOptions.WriteThrough))
|
||||||
|
{
|
||||||
File.WriteAllText(path, JsonHelper.Serialize(_titleUpdateWindowData, true));
|
dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
||||||
|
}
|
||||||
|
|
||||||
MainWindow.UpdateGameTable();
|
MainWindow.UpdateGameTable();
|
||||||
Dispose();
|
Dispose();
|
||||||
|
|
Loading…
Reference in a new issue