diff options
-rw-r--r-- | Ryujinx.Common/Configuration/DlcContainer.cs | 10 | ||||
-rw-r--r-- | Ryujinx.Common/Configuration/DlcNca.cs | 9 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/Content/ContentManager.cs | 13 | ||||
-rw-r--r-- | Ryujinx.HLE/HOS/ApplicationLoader.cs | 42 | ||||
-rw-r--r-- | Ryujinx/Ryujinx.csproj | 4 | ||||
-rw-r--r-- | Ryujinx/Ui/DlcWindow.cs | 244 | ||||
-rw-r--r-- | Ryujinx/Ui/DlcWindow.glade | 186 | ||||
-rw-r--r-- | Ryujinx/Ui/GameTableContextMenu.cs | 127 | ||||
-rw-r--r-- | Ryujinx/Ui/GameTableContextMenu.glade | 80 | ||||
-rw-r--r-- | Ryujinx/Ui/TitleUpdateWindow.cs | 30 |
10 files changed, 581 insertions, 164 deletions
diff --git a/Ryujinx.Common/Configuration/DlcContainer.cs b/Ryujinx.Common/Configuration/DlcContainer.cs new file mode 100644 index 00000000..c056ac1e --- /dev/null +++ b/Ryujinx.Common/Configuration/DlcContainer.cs @@ -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; } + } +}
\ No newline at end of file diff --git a/Ryujinx.Common/Configuration/DlcNca.cs b/Ryujinx.Common/Configuration/DlcNca.cs new file mode 100644 index 00000000..df6a4532 --- /dev/null +++ b/Ryujinx.Common/Configuration/DlcNca.cs @@ -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; } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs index 839078e8..1b9ea143 100644 --- a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs +++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs @@ -4,7 +4,6 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using LibHac.Ncm; -using LibHac.Spl; using Ryujinx.Common.Logging; using Ryujinx.HLE.Exceptions; 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 int GetAocCount() => _aocData.Where(e => e.Value.Enabled).Count(); diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs index 5ca67445..f6ab5ba9 100644 --- a/Ryujinx.HLE/HOS/ApplicationLoader.cs +++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs @@ -149,17 +149,6 @@ namespace Ryujinx.HLE.HOS _contentManager.ClearAocData(); _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); } @@ -196,18 +185,6 @@ namespace Ryujinx.HLE.HOS _contentManager.ClearAocData(); _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); return; @@ -238,7 +215,8 @@ namespace Ryujinx.HLE.HOS IStorage dataStorage = 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)) { @@ -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 (mainNca.CanOpenSection(NcaSectionType.Data)) diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index 30b368ab..cf672131 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -47,7 +47,7 @@ <None Remove="Ui\assets\Icon.png" /> <None Remove="Ui\assets\TwitterLogo.png" /> <None Remove="Ui\ControllerWindow.glade" /> - <None Remove="Ui\GameTableContextMenu.glade" /> + <None Remove="Ui\DlcWindow.glade" /> <None Remove="Ui\MainWindow.glade" /> <None Remove="Ui\ProfileDialog.glade" /> <None Remove="Ui\SettingsWindow.glade" /> @@ -71,10 +71,10 @@ <EmbeddedResource Include="Ui\assets\Icon.png" /> <EmbeddedResource Include="Ui\assets\TwitterLogo.png" /> <EmbeddedResource Include="Ui\ControllerWindow.glade" /> - <EmbeddedResource Include="Ui\GameTableContextMenu.glade" /> <EmbeddedResource Include="Ui\MainWindow.glade" /> <EmbeddedResource Include="Ui\ProfileDialog.glade" /> <EmbeddedResource Include="Ui\SettingsWindow.glade" /> + <EmbeddedResource Include="Ui\DlcWindow.glade" /> <EmbeddedResource Include="Ui\TitleUpdateWindow.glade" /> </ItemGroup> diff --git a/Ryujinx/Ui/DlcWindow.cs b/Ryujinx/Ui/DlcWindow.cs new file mode 100644 index 00000000..a81698fa --- /dev/null +++ b/Ryujinx/Ui/DlcWindow.cs @@ -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(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx/Ui/DlcWindow.glade b/Ryujinx/Ui/DlcWindow.glade new file mode 100644 index 00000000..08d7ca0a --- /dev/null +++ b/Ryujinx/Ui/DlcWindow.glade @@ -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> diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/GameTableContextMenu.cs index 2aee44d5..90ea44bd 100644 --- a/Ryujinx/Ui/GameTableContextMenu.cs +++ b/Ryujinx/Ui/GameTableContextMenu.cs @@ -8,7 +8,6 @@ using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using LibHac.Ncm; using LibHac.Ns; -using LibHac.Spl; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; @@ -20,64 +19,97 @@ using System.Globalization; using System.IO; using System.Reflection; using System.Threading; + using static LibHac.Fs.ApplicationSaveDataManagement; -using GUI = Gtk.Builder.ObjectAttribute; namespace Ryujinx.Ui { public class GameTableContextMenu : Menu { - private ListStore _gameTableStore; - private TreeIter _rowIter; - private VirtualFileSystem _virtualFileSystem; - private MessageDialog _dialog; - 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 + private readonly ListStore _gameTableStore; + private readonly TreeIter _rowIter; + private readonly 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 readonly BlitStruct<ApplicationControlProperty> _controlData; - private GameTableContextMenu(Builder builder, ListStore gameTableStore, BlitStruct<ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_contextMenu").Handle) - { - builder.Autoconnect(this); + private MessageDialog _dialog; + private bool _cancel; + public GameTableContextMenu(ListStore gameTableStore, BlitStruct<ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem) + { _gameTableStore = gameTableStore; _rowIter = rowIter; _virtualFileSystem = virtualFileSystem; _controlData = controlData; - _openSaveUserDir.Activated += OpenSaveUserDir_Clicked; - _openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked; - _openSaveBcatDir.Activated += OpenSaveBcatDir_Clicked; - _manageTitleUpdates.Activated += ManageTitleUpdates_Clicked; - _extractRomFs.Activated += ExtractRomFs_Clicked; - _extractExeFs.Activated += ExtractExeFs_Clicked; - _extractLogo.Activated += ExtractLogo_Clicked; + MenuItem openSaveUserDir = new MenuItem("Open User Save Directory") + { + Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0, + TooltipText = "Open the folder where the User save for the application is loaded" + }; - _openSaveUserDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0; - _openSaveDeviceDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0; - _openSaveBcatDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0; + MenuItem openSaveDeviceDir = new MenuItem("Open Device Save Directory") + { + Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0, + TooltipText = "Open the folder where the Device save for the application is loaded" + }; - string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower(); - if (ext != ".nca" && ext != ".nsp" && ext != ".pfs0" && ext != ".xci") + MenuItem openSaveBcatDir = new MenuItem("Open BCAT Save Directory") { - _extractRomFs.Sensitive = false; - _extractExeFs.Sensitive = false; - _extractLogo.Sensitive = false; - } + 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(); + bool hasNca = ext == ".nca" || ext == ".nsp" || ext == ".pfs0" || ext == ".xci"; + + MenuItem extractRomFs = new MenuItem("Extract RomFS Section") + { + Sensitive = hasNca, + TooltipText = "Exctact the RomFs section present in the main NCA" + }; + + 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) @@ -478,7 +510,7 @@ namespace Ryujinx.Ui string saveDir = GetSaveDataDirectory(saveDataId); - Process.Start(new ProcessStartInfo() + Process.Start(new ProcessStartInfo { FileName = saveDir, UseShellExecute = true, @@ -531,6 +563,15 @@ namespace Ryujinx.Ui 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) { ExtractSection(NcaSectionType.Data); diff --git a/Ryujinx/Ui/GameTableContextMenu.glade b/Ryujinx/Ui/GameTableContextMenu.glade deleted file mode 100644 index e648b745..00000000 --- a/Ryujinx/Ui/GameTableContextMenu.glade +++ /dev/null @@ -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> diff --git a/Ryujinx/Ui/TitleUpdateWindow.cs b/Ryujinx/Ui/TitleUpdateWindow.cs index acf0c2bd..b45b5df0 100644 --- a/Ryujinx/Ui/TitleUpdateWindow.cs +++ b/Ryujinx/Ui/TitleUpdateWindow.cs @@ -5,26 +5,27 @@ using LibHac.Fs; using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using LibHac.Ns; -using LibHac.Spl; 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 GUI = Gtk.Builder.ObjectAttribute; using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; namespace Ryujinx.Ui { public class TitleUpdateWindow : Window { - private readonly string _titleId; private readonly VirtualFileSystem _virtualFileSystem; + private readonly string _titleId; + private readonly string _updateJsonPath; - private TitleUpdateMetadata _titleUpdateWindowData; - private Dictionary<RadioButton, string> _radioButtonToPathDictionary = new Dictionary<RadioButton, string>(); + private TitleUpdateMetadata _titleUpdateWindowData; + private Dictionary<RadioButton, string> _radioButtonToPathDictionary; #pragma warning disable CS0649, IDE0044 [GUI] Label _baseTitleInfoLabel; @@ -38,14 +39,14 @@ namespace Ryujinx.Ui { builder.Autoconnect(this); - _titleId = titleId; - _virtualFileSystem = virtualFileSystem; + _titleId = titleId; + _virtualFileSystem = virtualFileSystem; + _updateJsonPath = System.IO.Path.Combine(_virtualFileSystem.GetBasePath(), "games", _titleId, "updates.json"); + _radioButtonToPathDictionary = new Dictionary<RadioButton, string>(); try { - string path = System.IO.Path.Combine(_virtualFileSystem.GetBasePath(), "games", _titleId, "updates.json"); - - _titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(path); + _titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_updateJsonPath); } 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) { @@ -194,9 +195,10 @@ namespace Ryujinx.Ui } } - string path = System.IO.Path.Combine(_virtualFileSystem.GetBasePath(), "games", _titleId, "updates.json"); - - File.WriteAllText(path, JsonHelper.Serialize(_titleUpdateWindowData, true)); + using (FileStream dlcJsonStream = File.Create(_updateJsonPath, 4096, FileOptions.WriteThrough)) + { + dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); + } MainWindow.UpdateGameTable(); Dispose(); |