ContentManager.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. using LibHac;
  2. using Ryujinx.HLE.Utilities;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. namespace Ryujinx.HLE.FileSystem.Content
  8. {
  9. internal class ContentManager
  10. {
  11. private Dictionary<StorageId, LinkedList<LocationEntry>> LocationEntries;
  12. private Dictionary<string, long> SharedFontTitleDictionary;
  13. private SortedDictionary<(ulong, ContentType), string> ContentDictionary;
  14. private Switch Device;
  15. public ContentManager(Switch Device)
  16. {
  17. ContentDictionary = new SortedDictionary<(ulong, ContentType), string>();
  18. LocationEntries = new Dictionary<StorageId, LinkedList<LocationEntry>>();
  19. SharedFontTitleDictionary = new Dictionary<string, long>()
  20. {
  21. { "FontStandard", 0x0100000000000811 },
  22. { "FontChineseSimplified", 0x0100000000000814 },
  23. { "FontExtendedChineseSimplified", 0x0100000000000814 },
  24. { "FontKorean", 0x0100000000000812 },
  25. { "FontChineseTraditional", 0x0100000000000813 },
  26. { "FontNintendoExtended" , 0x0100000000000810 },
  27. };
  28. this.Device = Device;
  29. }
  30. public void LoadEntries()
  31. {
  32. ContentDictionary = new SortedDictionary<(ulong, ContentType), string>();
  33. foreach (StorageId StorageId in Enum.GetValues(typeof(StorageId)))
  34. {
  35. string ContentDirectory = null;
  36. string ContentPathString = null;
  37. string RegisteredDirectory = null;
  38. try
  39. {
  40. ContentPathString = LocationHelper.GetContentRoot(StorageId);
  41. ContentDirectory = LocationHelper.GetRealPath(Device.FileSystem, ContentPathString);
  42. RegisteredDirectory = Path.Combine(ContentDirectory, "registered");
  43. }
  44. catch (NotSupportedException NEx)
  45. {
  46. continue;
  47. }
  48. Directory.CreateDirectory(RegisteredDirectory);
  49. LinkedList<LocationEntry> LocationList = new LinkedList<LocationEntry>();
  50. void AddEntry(LocationEntry Entry)
  51. {
  52. LocationList.AddLast(Entry);
  53. }
  54. foreach (string DirectoryPath in Directory.EnumerateDirectories(RegisteredDirectory))
  55. {
  56. if (Directory.GetFiles(DirectoryPath).Length > 0)
  57. {
  58. string NcaName = new DirectoryInfo(DirectoryPath).Name.Replace(".nca", string.Empty);
  59. using (FileStream NcaFile = new FileStream(Directory.GetFiles(DirectoryPath)[0], FileMode.Open, FileAccess.Read))
  60. {
  61. Nca Nca = new Nca(Device.System.KeySet, NcaFile, false);
  62. string SwitchPath = Path.Combine(ContentPathString + ":",
  63. NcaFile.Name.Replace(ContentDirectory, string.Empty).TrimStart('\\'));
  64. // Change path format to switch's
  65. SwitchPath = SwitchPath.Replace('\\', '/');
  66. LocationEntry Entry = new LocationEntry(SwitchPath,
  67. 0,
  68. (long)Nca.Header.TitleId,
  69. Nca.Header.ContentType);
  70. AddEntry(Entry);
  71. ContentDictionary.Add((Nca.Header.TitleId, Nca.Header.ContentType), NcaName);
  72. NcaFile.Close();
  73. Nca.Dispose();
  74. NcaFile.Dispose();
  75. }
  76. }
  77. }
  78. foreach (string FilePath in Directory.EnumerateFiles(ContentDirectory))
  79. {
  80. if (Path.GetExtension(FilePath) == ".nca")
  81. {
  82. string NcaName = Path.GetFileNameWithoutExtension(FilePath);
  83. using (FileStream NcaFile = new FileStream(FilePath, FileMode.Open, FileAccess.Read))
  84. {
  85. Nca Nca = new Nca(Device.System.KeySet, NcaFile, false);
  86. string SwitchPath = Path.Combine(ContentPathString + ":",
  87. FilePath.Replace(ContentDirectory, string.Empty).TrimStart('\\'));
  88. // Change path format to switch's
  89. SwitchPath = SwitchPath.Replace('\\', '/');
  90. LocationEntry Entry = new LocationEntry(SwitchPath,
  91. 0,
  92. (long)Nca.Header.TitleId,
  93. Nca.Header.ContentType);
  94. AddEntry(Entry);
  95. ContentDictionary.Add((Nca.Header.TitleId, Nca.Header.ContentType), NcaName);
  96. NcaFile.Close();
  97. Nca.Dispose();
  98. NcaFile.Dispose();
  99. }
  100. }
  101. }
  102. if(LocationEntries.ContainsKey(StorageId) && LocationEntries[StorageId]?.Count == 0)
  103. {
  104. LocationEntries.Remove(StorageId);
  105. }
  106. if (!LocationEntries.ContainsKey(StorageId))
  107. {
  108. LocationEntries.Add(StorageId, LocationList);
  109. }
  110. }
  111. }
  112. public void ClearEntry(long TitleId, ContentType ContentType,StorageId StorageId)
  113. {
  114. RemoveLocationEntry(TitleId, ContentType, StorageId);
  115. }
  116. public void RefreshEntries(StorageId StorageId, int Flag)
  117. {
  118. LinkedList<LocationEntry> LocationList = LocationEntries[StorageId];
  119. LinkedListNode<LocationEntry> LocationEntry = LocationList.First;
  120. while (LocationEntry != null)
  121. {
  122. LinkedListNode<LocationEntry> NextLocationEntry = LocationEntry.Next;
  123. if (LocationEntry.Value.Flag == Flag)
  124. {
  125. LocationList.Remove(LocationEntry.Value);
  126. }
  127. LocationEntry = NextLocationEntry;
  128. }
  129. }
  130. public bool HasNca(string NcaId, StorageId StorageId)
  131. {
  132. if (ContentDictionary.ContainsValue(NcaId))
  133. {
  134. var Content = ContentDictionary.FirstOrDefault(x => x.Value == NcaId);
  135. long TitleId = (long)Content.Key.Item1;
  136. ContentType ContentType = Content.Key.Item2;
  137. StorageId Storage = GetInstalledStorage(TitleId, ContentType, StorageId);
  138. return Storage == StorageId;
  139. }
  140. return false;
  141. }
  142. public UInt128 GetInstalledNcaId(long TitleId, ContentType ContentType)
  143. {
  144. if (ContentDictionary.ContainsKey(((ulong)TitleId,ContentType)))
  145. {
  146. return new UInt128(ContentDictionary[((ulong)TitleId,ContentType)]);
  147. }
  148. return new UInt128();
  149. }
  150. public StorageId GetInstalledStorage(long TitleId, ContentType ContentType, StorageId StorageId)
  151. {
  152. LocationEntry LocationEntry = GetLocation(TitleId, ContentType, StorageId);
  153. return LocationEntry.ContentPath != null ?
  154. LocationHelper.GetStorageId(LocationEntry.ContentPath) : StorageId.None;
  155. }
  156. public string GetInstalledContentPath(long TitleId, StorageId StorageId, ContentType ContentType)
  157. {
  158. LocationEntry LocationEntry = GetLocation(TitleId, ContentType, StorageId);
  159. if (VerifyContentType(LocationEntry, ContentType))
  160. {
  161. return LocationEntry.ContentPath;
  162. }
  163. return string.Empty;
  164. }
  165. public void RedirectLocation(LocationEntry NewEntry, StorageId StorageId)
  166. {
  167. LocationEntry LocationEntry = GetLocation(NewEntry.TitleId, NewEntry.ContentType, StorageId);
  168. if (LocationEntry.ContentPath != null)
  169. {
  170. RemoveLocationEntry(NewEntry.TitleId, NewEntry.ContentType, StorageId);
  171. }
  172. AddLocationEntry(NewEntry, StorageId);
  173. }
  174. private bool VerifyContentType(LocationEntry LocationEntry, ContentType ContentType)
  175. {
  176. StorageId StorageId = LocationHelper.GetStorageId(LocationEntry.ContentPath);
  177. string InstalledPath = Device.FileSystem.SwitchPathToSystemPath(LocationEntry.ContentPath);
  178. if (!string.IsNullOrWhiteSpace(InstalledPath))
  179. {
  180. if (File.Exists(InstalledPath))
  181. {
  182. FileStream File = new FileStream(InstalledPath, FileMode.Open, FileAccess.Read);
  183. Nca Nca = new Nca(Device.System.KeySet, File, false);
  184. bool ContentCheck = Nca.Header.ContentType == ContentType;
  185. Nca.Dispose();
  186. File.Dispose();
  187. return ContentCheck;
  188. }
  189. }
  190. return false;
  191. }
  192. private void AddLocationEntry(LocationEntry Entry, StorageId StorageId)
  193. {
  194. LinkedList<LocationEntry> LocationList = null;
  195. if (LocationEntries.ContainsKey(StorageId))
  196. {
  197. LocationList = LocationEntries[StorageId];
  198. }
  199. if (LocationList != null)
  200. {
  201. if (LocationList.Contains(Entry))
  202. {
  203. LocationList.Remove(Entry);
  204. }
  205. LocationList.AddLast(Entry);
  206. }
  207. }
  208. private void RemoveLocationEntry(long TitleId, ContentType ContentType, StorageId StorageId)
  209. {
  210. LinkedList<LocationEntry> LocationList = null;
  211. if (LocationEntries.ContainsKey(StorageId))
  212. {
  213. LocationList = LocationEntries[StorageId];
  214. }
  215. if (LocationList != null)
  216. {
  217. LocationEntry Entry =
  218. LocationList.ToList().Find(x => x.TitleId == TitleId && x.ContentType == ContentType);
  219. if (Entry.ContentPath != null)
  220. {
  221. LocationList.Remove(Entry);
  222. }
  223. }
  224. }
  225. public bool TryGetFontTitle(string FontName, out long TitleId)
  226. {
  227. return SharedFontTitleDictionary.TryGetValue(FontName, out TitleId);
  228. }
  229. private LocationEntry GetLocation(long TitleId, ContentType ContentType,StorageId StorageId)
  230. {
  231. LinkedList<LocationEntry> LocationList = LocationEntries[StorageId];
  232. return LocationList.ToList().Find(x => x.TitleId == TitleId && x.ContentType == ContentType);
  233. }
  234. }
  235. }