DlcWindow.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. using Gtk;
  2. using LibHac;
  3. using LibHac.Common;
  4. using LibHac.Fs;
  5. using LibHac.Fs.Fsa;
  6. using LibHac.FsSystem;
  7. using LibHac.FsSystem.NcaUtils;
  8. using Ryujinx.Common.Configuration;
  9. using Ryujinx.Common.Logging;
  10. using Ryujinx.HLE.FileSystem;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.IO;
  14. using System.Text;
  15. using GUI = Gtk.Builder.ObjectAttribute;
  16. using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
  17. namespace Ryujinx.Ui
  18. {
  19. public class DlcWindow : Window
  20. {
  21. private readonly VirtualFileSystem _virtualFileSystem;
  22. private readonly string _titleId;
  23. private readonly string _dlcJsonPath;
  24. private readonly List<DlcContainer> _dlcContainerList;
  25. #pragma warning disable CS0649, IDE0044
  26. [GUI] Label _baseTitleInfoLabel;
  27. [GUI] TreeView _dlcTreeView;
  28. [GUI] TreeSelection _dlcTreeSelection;
  29. #pragma warning restore CS0649, IDE0044
  30. public DlcWindow(string titleId, string titleName, VirtualFileSystem virtualFileSystem) : this(new Builder("Ryujinx.Ui.DlcWindow.glade"), titleId, titleName, virtualFileSystem) { }
  31. private DlcWindow(Builder builder, string titleId, string titleName, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_dlcWindow").Handle)
  32. {
  33. builder.Autoconnect(this);
  34. _titleId = titleId;
  35. _virtualFileSystem = virtualFileSystem;
  36. _dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json");
  37. _baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]";
  38. try
  39. {
  40. _dlcContainerList = JsonHelper.DeserializeFromFile<List<DlcContainer>>(_dlcJsonPath);
  41. }
  42. catch
  43. {
  44. _dlcContainerList = new List<DlcContainer>();
  45. }
  46. _dlcTreeView.Model = new TreeStore(
  47. typeof(bool),
  48. typeof(string),
  49. typeof(string));
  50. CellRendererToggle enableToggle = new CellRendererToggle();
  51. enableToggle.Toggled += (sender, args) =>
  52. {
  53. _dlcTreeView.Model.GetIter(out TreeIter treeIter, new TreePath(args.Path));
  54. bool newValue = !(bool)_dlcTreeView.Model.GetValue(treeIter, 0);
  55. _dlcTreeView.Model.SetValue(treeIter, 0, newValue);
  56. if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, treeIter))
  57. {
  58. do
  59. {
  60. _dlcTreeView.Model.SetValue(childIter, 0, newValue);
  61. }
  62. while (_dlcTreeView.Model.IterNext(ref childIter));
  63. }
  64. };
  65. _dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0);
  66. _dlcTreeView.AppendColumn("TitleId", new CellRendererText(), "text", 1);
  67. _dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2);
  68. foreach (DlcContainer dlcContainer in _dlcContainerList)
  69. {
  70. TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", dlcContainer.Path);
  71. using FileStream containerFile = File.OpenRead(dlcContainer.Path);
  72. PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
  73. _virtualFileSystem.ImportTickets(pfs);
  74. foreach (DlcNca dlcNca in dlcContainer.DlcNcaList)
  75. {
  76. pfs.OpenFile(out IFile ncaFile, dlcNca.Path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
  77. Nca nca = TryCreateNca(ncaFile.AsStorage(), dlcContainer.Path);
  78. if (nca != null)
  79. {
  80. ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter, dlcNca.Enabled, nca.Header.TitleId.ToString("X16"), dlcNca.Path);
  81. }
  82. }
  83. }
  84. }
  85. private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
  86. {
  87. try
  88. {
  89. return new Nca(_virtualFileSystem.KeySet, ncaStorage);
  90. }
  91. catch (InvalidDataException exception)
  92. {
  93. Logger.Error?.Print(LogClass.Application, $"{exception.Message}. Errored File: {containerPath}");
  94. 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.");
  95. }
  96. catch (MissingKeyException exception)
  97. {
  98. Logger.Error?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {containerPath}");
  99. GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add DLC Failed!", $"Your key set is missing a key with the name: {exception.Name}");
  100. }
  101. return null;
  102. }
  103. private void AddButton_Clicked(object sender, EventArgs args)
  104. {
  105. FileChooserDialog fileChooser = new FileChooserDialog("Select DLC files", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Add", ResponseType.Accept)
  106. {
  107. SelectMultiple = true,
  108. Filter = new FileFilter()
  109. };
  110. fileChooser.SetPosition(WindowPosition.Center);
  111. fileChooser.Filter.AddPattern("*.nsp");
  112. if (fileChooser.Run() == (int)ResponseType.Accept)
  113. {
  114. foreach (string containerPath in fileChooser.Filenames)
  115. {
  116. if (!File.Exists(containerPath))
  117. {
  118. return;
  119. }
  120. using (FileStream containerFile = File.OpenRead(containerPath))
  121. {
  122. PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
  123. bool containsDlc = false;
  124. _virtualFileSystem.ImportTickets(pfs);
  125. TreeIter? parentIter = null;
  126. foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
  127. {
  128. pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
  129. Nca nca = TryCreateNca(ncaFile.AsStorage(), containerPath);
  130. if (nca == null) continue;
  131. if (nca.Header.ContentType == NcaContentType.PublicData)
  132. {
  133. if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != _titleId)
  134. {
  135. break;
  136. }
  137. parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath);
  138. ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
  139. containsDlc = true;
  140. }
  141. }
  142. if (!containsDlc)
  143. {
  144. GtkDialog.CreateErrorDialog("The specified file does not contain a DLC for the selected title!");
  145. }
  146. }
  147. }
  148. }
  149. fileChooser.Dispose();
  150. }
  151. private void RemoveButton_Clicked(object sender, EventArgs args)
  152. {
  153. if (_dlcTreeSelection.GetSelected(out ITreeModel treeModel, out TreeIter treeIter))
  154. {
  155. if (_dlcTreeView.Model.IterParent(out TreeIter parentIter, treeIter) && _dlcTreeView.Model.IterNChildren(parentIter) <= 1)
  156. {
  157. ((TreeStore)treeModel).Remove(ref parentIter);
  158. }
  159. else
  160. {
  161. ((TreeStore)treeModel).Remove(ref treeIter);
  162. }
  163. }
  164. }
  165. private void SaveButton_Clicked(object sender, EventArgs args)
  166. {
  167. _dlcContainerList.Clear();
  168. if (_dlcTreeView.Model.GetIterFirst(out TreeIter parentIter))
  169. {
  170. do
  171. {
  172. if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, parentIter))
  173. {
  174. DlcContainer dlcContainer = new DlcContainer
  175. {
  176. Path = (string)_dlcTreeView.Model.GetValue(parentIter, 2),
  177. DlcNcaList = new List<DlcNca>()
  178. };
  179. do
  180. {
  181. dlcContainer.DlcNcaList.Add(new DlcNca
  182. {
  183. Enabled = (bool)_dlcTreeView.Model.GetValue(childIter, 0),
  184. TitleId = Convert.ToUInt64(_dlcTreeView.Model.GetValue(childIter, 1).ToString(), 16),
  185. Path = (string)_dlcTreeView.Model.GetValue(childIter, 2)
  186. });
  187. }
  188. while (_dlcTreeView.Model.IterNext(ref childIter));
  189. _dlcContainerList.Add(dlcContainer);
  190. }
  191. }
  192. while (_dlcTreeView.Model.IterNext(ref parentIter));
  193. }
  194. using (FileStream dlcJsonStream = File.Create(_dlcJsonPath, 4096, FileOptions.WriteThrough))
  195. {
  196. dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_dlcContainerList, true)));
  197. }
  198. Dispose();
  199. }
  200. private void CancelButton_Clicked(object sender, EventArgs args)
  201. {
  202. Dispose();
  203. }
  204. }
  205. }