ContentManager.cs 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. using LibHac;
  2. using LibHac.Common;
  3. using LibHac.Fs;
  4. using LibHac.FsSystem;
  5. using LibHac.FsSystem.NcaUtils;
  6. using LibHac.Ncm;
  7. using Ryujinx.Common.Logging;
  8. using Ryujinx.HLE.Exceptions;
  9. using Ryujinx.HLE.HOS.Services.Time;
  10. using Ryujinx.HLE.Utilities;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.IO;
  14. using System.IO.Compression;
  15. using System.Linq;
  16. namespace Ryujinx.HLE.FileSystem.Content
  17. {
  18. public class ContentManager
  19. {
  20. private const ulong SystemVersionTitleId = 0x0100000000000809;
  21. private const ulong SystemUpdateTitleId = 0x0100000000000816;
  22. private Dictionary<StorageId, LinkedList<LocationEntry>> _locationEntries;
  23. private Dictionary<string, long> _sharedFontTitleDictionary;
  24. private Dictionary<long, string> _systemTitlesNameDictionary;
  25. private Dictionary<string, string> _sharedFontFilenameDictionary;
  26. private SortedDictionary<(ulong titleId, NcaContentType type), string> _contentDictionary;
  27. private struct AocItem
  28. {
  29. public readonly string ContainerPath;
  30. public readonly string NcaPath;
  31. public bool Enabled;
  32. public AocItem(string containerPath, string ncaPath, bool enabled)
  33. {
  34. ContainerPath = containerPath;
  35. NcaPath = ncaPath;
  36. Enabled = enabled;
  37. }
  38. }
  39. private SortedList<ulong, AocItem> _aocData { get; }
  40. private VirtualFileSystem _virtualFileSystem;
  41. private readonly object _lock = new object();
  42. public ContentManager(VirtualFileSystem virtualFileSystem)
  43. {
  44. _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>();
  45. _locationEntries = new Dictionary<StorageId, LinkedList<LocationEntry>>();
  46. _sharedFontTitleDictionary = new Dictionary<string, long>
  47. {
  48. { "FontStandard", 0x0100000000000811 },
  49. { "FontChineseSimplified", 0x0100000000000814 },
  50. { "FontExtendedChineseSimplified", 0x0100000000000814 },
  51. { "FontKorean", 0x0100000000000812 },
  52. { "FontChineseTraditional", 0x0100000000000813 },
  53. { "FontNintendoExtended", 0x0100000000000810 }
  54. };
  55. _systemTitlesNameDictionary = new Dictionary<long, string>()
  56. {
  57. { 0x010000000000080E, "TimeZoneBinary" },
  58. { 0x0100000000000810, "FontNintendoExtension" },
  59. { 0x0100000000000811, "FontStandard" },
  60. { 0x0100000000000812, "FontKorean" },
  61. { 0x0100000000000813, "FontChineseTraditional" },
  62. { 0x0100000000000814, "FontChineseSimple" },
  63. };
  64. _sharedFontFilenameDictionary = new Dictionary<string, string>
  65. {
  66. { "FontStandard", "nintendo_udsg-r_std_003.bfttf" },
  67. { "FontChineseSimplified", "nintendo_udsg-r_org_zh-cn_003.bfttf" },
  68. { "FontExtendedChineseSimplified", "nintendo_udsg-r_ext_zh-cn_003.bfttf" },
  69. { "FontKorean", "nintendo_udsg-r_ko_003.bfttf" },
  70. { "FontChineseTraditional", "nintendo_udjxh-db_zh-tw_003.bfttf" },
  71. { "FontNintendoExtended", "nintendo_ext_003.bfttf" }
  72. };
  73. _virtualFileSystem = virtualFileSystem;
  74. _aocData = new SortedList<ulong, AocItem>();
  75. }
  76. public void LoadEntries(Switch device = null)
  77. {
  78. lock (_lock)
  79. {
  80. _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>();
  81. _locationEntries = new Dictionary<StorageId, LinkedList<LocationEntry>>();
  82. foreach (StorageId storageId in Enum.GetValues(typeof(StorageId)))
  83. {
  84. string contentDirectory = null;
  85. string contentPathString = null;
  86. string registeredDirectory = null;
  87. try
  88. {
  89. contentPathString = LocationHelper.GetContentRoot(storageId);
  90. contentDirectory = LocationHelper.GetRealPath(_virtualFileSystem, contentPathString);
  91. registeredDirectory = Path.Combine(contentDirectory, "registered");
  92. }
  93. catch (NotSupportedException)
  94. {
  95. continue;
  96. }
  97. Directory.CreateDirectory(registeredDirectory);
  98. LinkedList<LocationEntry> locationList = new LinkedList<LocationEntry>();
  99. void AddEntry(LocationEntry entry)
  100. {
  101. locationList.AddLast(entry);
  102. }
  103. foreach (string directoryPath in Directory.EnumerateDirectories(registeredDirectory))
  104. {
  105. if (Directory.GetFiles(directoryPath).Length > 0)
  106. {
  107. string ncaName = new DirectoryInfo(directoryPath).Name.Replace(".nca", string.Empty);
  108. using (FileStream ncaFile = File.OpenRead(Directory.GetFiles(directoryPath)[0]))
  109. {
  110. Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
  111. string switchPath = contentPathString + ":/" + ncaFile.Name.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar);
  112. // Change path format to switch's
  113. switchPath = switchPath.Replace('\\', '/');
  114. LocationEntry entry = new LocationEntry(switchPath,
  115. 0,
  116. (long)nca.Header.TitleId,
  117. nca.Header.ContentType);
  118. AddEntry(entry);
  119. _contentDictionary.Add((nca.Header.TitleId, nca.Header.ContentType), ncaName);
  120. }
  121. }
  122. }
  123. foreach (string filePath in Directory.EnumerateFiles(contentDirectory))
  124. {
  125. if (Path.GetExtension(filePath) == ".nca")
  126. {
  127. string ncaName = Path.GetFileNameWithoutExtension(filePath);
  128. using (FileStream ncaFile = new FileStream(filePath, FileMode.Open, FileAccess.Read))
  129. {
  130. Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
  131. string switchPath = contentPathString + ":/" + filePath.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar);
  132. // Change path format to switch's
  133. switchPath = switchPath.Replace('\\', '/');
  134. LocationEntry entry = new LocationEntry(switchPath,
  135. 0,
  136. (long)nca.Header.TitleId,
  137. nca.Header.ContentType);
  138. AddEntry(entry);
  139. _contentDictionary.Add((nca.Header.TitleId, nca.Header.ContentType), ncaName);
  140. }
  141. }
  142. }
  143. if (_locationEntries.ContainsKey(storageId) && _locationEntries[storageId]?.Count == 0)
  144. {
  145. _locationEntries.Remove(storageId);
  146. }
  147. if (!_locationEntries.ContainsKey(storageId))
  148. {
  149. _locationEntries.Add(storageId, locationList);
  150. }
  151. }
  152. if (device != null)
  153. {
  154. TimeManager.Instance.InitializeTimeZone(device);
  155. device.System.Font.Initialize(this);
  156. }
  157. }
  158. }
  159. // fs must contain AOC nca files in its root
  160. public void AddAocData(IFileSystem fs, string containerPath, ulong aocBaseId)
  161. {
  162. _virtualFileSystem.ImportTickets(fs);
  163. foreach (var ncaPath in fs.EnumerateEntries("*.cnmt.nca", SearchOptions.Default))
  164. {
  165. fs.OpenFile(out IFile ncaFile, ncaPath.FullPath.ToU8Span(), OpenMode.Read);
  166. using (ncaFile)
  167. {
  168. var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
  169. if (nca.Header.ContentType != NcaContentType.Meta)
  170. {
  171. Logger.PrintWarning(LogClass.Application, $"{ncaPath} is not a valid metadata file");
  172. continue;
  173. }
  174. using var pfs0 = nca.OpenFileSystem(0, Switch.GetIntegrityCheckLevel());
  175. pfs0.OpenFile(out IFile cnmtFile, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read);
  176. using (cnmtFile)
  177. {
  178. var cnmt = new Cnmt(cnmtFile.AsStream());
  179. if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
  180. {
  181. continue;
  182. }
  183. string ncaId = BitConverter.ToString(cnmt.ContentEntries[0].NcaId).Replace("-", "").ToLower();
  184. if (!_aocData.TryAdd(cnmt.TitleId, new AocItem(containerPath, $"{ncaId}.nca", true)))
  185. {
  186. Logger.PrintWarning(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {cnmt.TitleId:X16}");
  187. }
  188. else
  189. {
  190. Logger.PrintInfo(LogClass.Application, $"Found AddOnContent with TitleId {cnmt.TitleId:X16}");
  191. }
  192. }
  193. }
  194. }
  195. }
  196. public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool enabled)
  197. {
  198. if (!_aocData.TryAdd(titleId, new AocItem(containerPath, ncaPath, enabled)))
  199. {
  200. Logger.PrintWarning(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {titleId:X16}");
  201. }
  202. else
  203. {
  204. Logger.PrintInfo(LogClass.Application, $"Found AddOnContent with TitleId {titleId:X16}");
  205. using (FileStream fileStream = File.OpenRead(containerPath))
  206. using (PartitionFileSystem pfs = new PartitionFileSystem(fileStream.AsStorage()))
  207. {
  208. _virtualFileSystem.ImportTickets(pfs);
  209. }
  210. }
  211. }
  212. public void ClearAocData() => _aocData.Clear();
  213. public int GetAocCount() => _aocData.Where(e => e.Value.Enabled).Count();
  214. public IList<ulong> GetAocTitleIds() => _aocData.Where(e => e.Value.Enabled).Select(e => e.Key).ToList();
  215. public bool GetAocDataStorage(ulong aocTitleId, out IStorage aocStorage)
  216. {
  217. aocStorage = null;
  218. if (_aocData.TryGetValue(aocTitleId, out AocItem aoc) && aoc.Enabled)
  219. {
  220. var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
  221. PartitionFileSystem pfs;
  222. IFile ncaFile;
  223. switch (Path.GetExtension(aoc.ContainerPath))
  224. {
  225. case ".xci":
  226. pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
  227. pfs.OpenFile(out ncaFile, aoc.NcaPath.ToU8Span(), OpenMode.Read);
  228. break;
  229. case ".nsp":
  230. pfs = new PartitionFileSystem(file.AsStorage());
  231. pfs.OpenFile(out ncaFile, aoc.NcaPath.ToU8Span(), OpenMode.Read);
  232. break;
  233. default:
  234. return false; // Print error?
  235. }
  236. aocStorage = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage()).OpenStorage(NcaSectionType.Data, Switch.GetIntegrityCheckLevel());
  237. return true;
  238. }
  239. return false;
  240. }
  241. public void ClearEntry(long titleId, NcaContentType contentType, StorageId storageId)
  242. {
  243. lock (_lock)
  244. {
  245. RemoveLocationEntry(titleId, contentType, storageId);
  246. }
  247. }
  248. public void RefreshEntries(StorageId storageId, int flag)
  249. {
  250. lock (_lock)
  251. {
  252. LinkedList<LocationEntry> locationList = _locationEntries[storageId];
  253. LinkedListNode<LocationEntry> locationEntry = locationList.First;
  254. while (locationEntry != null)
  255. {
  256. LinkedListNode<LocationEntry> nextLocationEntry = locationEntry.Next;
  257. if (locationEntry.Value.Flag == flag)
  258. {
  259. locationList.Remove(locationEntry.Value);
  260. }
  261. locationEntry = nextLocationEntry;
  262. }
  263. }
  264. }
  265. public bool HasNca(string ncaId, StorageId storageId)
  266. {
  267. lock (_lock)
  268. {
  269. if (_contentDictionary.ContainsValue(ncaId))
  270. {
  271. var content = _contentDictionary.FirstOrDefault(x => x.Value == ncaId);
  272. long titleId = (long)content.Key.Item1;
  273. NcaContentType contentType = content.Key.type;
  274. StorageId storage = GetInstalledStorage(titleId, contentType, storageId);
  275. return storage == storageId;
  276. }
  277. }
  278. return false;
  279. }
  280. public UInt128 GetInstalledNcaId(long titleId, NcaContentType contentType)
  281. {
  282. lock (_lock)
  283. {
  284. if (_contentDictionary.ContainsKey(((ulong)titleId, contentType)))
  285. {
  286. return new UInt128(_contentDictionary[((ulong)titleId, contentType)]);
  287. }
  288. }
  289. return new UInt128();
  290. }
  291. public StorageId GetInstalledStorage(long titleId, NcaContentType contentType, StorageId storageId)
  292. {
  293. lock (_lock)
  294. {
  295. LocationEntry locationEntry = GetLocation(titleId, contentType, storageId);
  296. return locationEntry.ContentPath != null ?
  297. LocationHelper.GetStorageId(locationEntry.ContentPath) : StorageId.None;
  298. }
  299. }
  300. public string GetInstalledContentPath(long titleId, StorageId storageId, NcaContentType contentType)
  301. {
  302. lock (_lock)
  303. {
  304. LocationEntry locationEntry = GetLocation(titleId, contentType, storageId);
  305. if (VerifyContentType(locationEntry, contentType))
  306. {
  307. return locationEntry.ContentPath;
  308. }
  309. }
  310. return string.Empty;
  311. }
  312. public void RedirectLocation(LocationEntry newEntry, StorageId storageId)
  313. {
  314. lock (_lock)
  315. {
  316. LocationEntry locationEntry = GetLocation(newEntry.TitleId, newEntry.ContentType, storageId);
  317. if (locationEntry.ContentPath != null)
  318. {
  319. RemoveLocationEntry(newEntry.TitleId, newEntry.ContentType, storageId);
  320. }
  321. AddLocationEntry(newEntry, storageId);
  322. }
  323. }
  324. private bool VerifyContentType(LocationEntry locationEntry, NcaContentType contentType)
  325. {
  326. if (locationEntry.ContentPath == null)
  327. {
  328. return false;
  329. }
  330. string installedPath = _virtualFileSystem.SwitchPathToSystemPath(locationEntry.ContentPath);
  331. if (!string.IsNullOrWhiteSpace(installedPath))
  332. {
  333. if (File.Exists(installedPath))
  334. {
  335. using (FileStream file = new FileStream(installedPath, FileMode.Open, FileAccess.Read))
  336. {
  337. Nca nca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
  338. bool contentCheck = nca.Header.ContentType == contentType;
  339. return contentCheck;
  340. }
  341. }
  342. }
  343. return false;
  344. }
  345. private void AddLocationEntry(LocationEntry entry, StorageId storageId)
  346. {
  347. LinkedList<LocationEntry> locationList = null;
  348. if (_locationEntries.ContainsKey(storageId))
  349. {
  350. locationList = _locationEntries[storageId];
  351. }
  352. if (locationList != null)
  353. {
  354. if (locationList.Contains(entry))
  355. {
  356. locationList.Remove(entry);
  357. }
  358. locationList.AddLast(entry);
  359. }
  360. }
  361. private void RemoveLocationEntry(long titleId, NcaContentType contentType, StorageId storageId)
  362. {
  363. LinkedList<LocationEntry> locationList = null;
  364. if (_locationEntries.ContainsKey(storageId))
  365. {
  366. locationList = _locationEntries[storageId];
  367. }
  368. if (locationList != null)
  369. {
  370. LocationEntry entry =
  371. locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType);
  372. if (entry.ContentPath != null)
  373. {
  374. locationList.Remove(entry);
  375. }
  376. }
  377. }
  378. public bool TryGetFontTitle(string fontName, out long titleId)
  379. {
  380. return _sharedFontTitleDictionary.TryGetValue(fontName, out titleId);
  381. }
  382. public bool TryGetFontFilename(string fontName, out string filename)
  383. {
  384. return _sharedFontFilenameDictionary.TryGetValue(fontName, out filename);
  385. }
  386. public bool TryGetSystemTitlesName(long titleId, out string name)
  387. {
  388. return _systemTitlesNameDictionary.TryGetValue(titleId, out name);
  389. }
  390. private LocationEntry GetLocation(long titleId, NcaContentType contentType, StorageId storageId)
  391. {
  392. LinkedList<LocationEntry> locationList = _locationEntries[storageId];
  393. return locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType);
  394. }
  395. public void InstallFirmware(string firmwareSource)
  396. {
  397. string contentPathString = LocationHelper.GetContentRoot(StorageId.NandSystem);
  398. string contentDirectory = LocationHelper.GetRealPath(_virtualFileSystem, contentPathString);
  399. string registeredDirectory = Path.Combine(contentDirectory, "registered");
  400. string temporaryDirectory = Path.Combine(contentDirectory, "temp");
  401. if (Directory.Exists(temporaryDirectory))
  402. {
  403. Directory.Delete(temporaryDirectory, true);
  404. }
  405. if (Directory.Exists(firmwareSource))
  406. {
  407. InstallFromDirectory(firmwareSource, temporaryDirectory);
  408. FinishInstallation(temporaryDirectory, registeredDirectory);
  409. return;
  410. }
  411. if (!File.Exists(firmwareSource))
  412. {
  413. throw new FileNotFoundException("Firmware file does not exist.");
  414. }
  415. FileInfo info = new FileInfo(firmwareSource);
  416. using (FileStream file = File.OpenRead(firmwareSource))
  417. {
  418. switch (info.Extension)
  419. {
  420. case ".zip":
  421. using (ZipArchive archive = ZipFile.OpenRead(firmwareSource))
  422. {
  423. InstallFromZip(archive, temporaryDirectory);
  424. }
  425. break;
  426. case ".xci":
  427. Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage());
  428. InstallFromCart(xci, temporaryDirectory);
  429. break;
  430. default:
  431. throw new InvalidFirmwarePackageException("Input file is not a valid firmware package");
  432. }
  433. FinishInstallation(temporaryDirectory, registeredDirectory);
  434. }
  435. }
  436. private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
  437. {
  438. if (Directory.Exists(registeredDirectory))
  439. {
  440. new DirectoryInfo(registeredDirectory).Delete(true);
  441. }
  442. Directory.Move(temporaryDirectory, registeredDirectory);
  443. LoadEntries();
  444. }
  445. private void InstallFromDirectory(string firmwareDirectory, string temporaryDirectory)
  446. {
  447. InstallFromPartition(new LocalFileSystem(firmwareDirectory), temporaryDirectory);
  448. }
  449. private void InstallFromPartition(IFileSystem filesystem, string temporaryDirectory)
  450. {
  451. foreach (var entry in filesystem.EnumerateEntries("/", "*.nca"))
  452. {
  453. Nca nca = new Nca(_virtualFileSystem.KeySet, OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage());
  454. SaveNca(nca, entry.Name.Remove(entry.Name.IndexOf('.')), temporaryDirectory);
  455. }
  456. }
  457. private void InstallFromCart(Xci gameCard, string temporaryDirectory)
  458. {
  459. if (gameCard.HasPartition(XciPartitionType.Update))
  460. {
  461. XciPartition partition = gameCard.OpenPartition(XciPartitionType.Update);
  462. InstallFromPartition(partition, temporaryDirectory);
  463. }
  464. else
  465. {
  466. throw new Exception("Update not found in xci file.");
  467. }
  468. }
  469. private void InstallFromZip(ZipArchive archive, string temporaryDirectory)
  470. {
  471. using (archive)
  472. {
  473. foreach (var entry in archive.Entries)
  474. {
  475. if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00"))
  476. {
  477. // Clean up the name and get the NcaId
  478. string[] pathComponents = entry.FullName.Replace(".cnmt", "").Split('/');
  479. string ncaId = pathComponents[pathComponents.Length - 1];
  480. // If this is a fragmented nca, we need to get the previous element.GetZip
  481. if (ncaId.Equals("00"))
  482. {
  483. ncaId = pathComponents[pathComponents.Length - 2];
  484. }
  485. if (ncaId.Contains(".nca"))
  486. {
  487. string newPath = Path.Combine(temporaryDirectory, ncaId);
  488. Directory.CreateDirectory(newPath);
  489. entry.ExtractToFile(Path.Combine(newPath, "00"));
  490. }
  491. }
  492. }
  493. }
  494. }
  495. public void SaveNca(Nca nca, string ncaId, string temporaryDirectory)
  496. {
  497. string newPath = Path.Combine(temporaryDirectory, ncaId + ".nca");
  498. Directory.CreateDirectory(newPath);
  499. using (FileStream file = File.Create(Path.Combine(newPath, "00")))
  500. {
  501. nca.BaseStorage.AsStream().CopyTo(file);
  502. }
  503. }
  504. private IFile OpenPossibleFragmentedFile(IFileSystem filesystem, string path, OpenMode mode)
  505. {
  506. IFile file;
  507. if (filesystem.FileExists($"{path}/00"))
  508. {
  509. filesystem.OpenFile(out file, $"{path}/00".ToU8Span(), mode);
  510. }
  511. else
  512. {
  513. filesystem.OpenFile(out file, path.ToU8Span(), mode);
  514. }
  515. return file;
  516. }
  517. private Stream GetZipStream(ZipArchiveEntry entry)
  518. {
  519. MemoryStream dest = new MemoryStream();
  520. Stream src = entry.Open();
  521. src.CopyTo(dest);
  522. src.Dispose();
  523. return dest;
  524. }
  525. public SystemVersion VerifyFirmwarePackage(string firmwarePackage)
  526. {
  527. Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new Dictionary<ulong, List<(NcaContentType, string)>>();
  528. if (Directory.Exists(firmwarePackage))
  529. {
  530. return VerifyAndGetVersionDirectory(firmwarePackage);
  531. }
  532. if (!File.Exists(firmwarePackage))
  533. {
  534. throw new FileNotFoundException("Firmware file does not exist.");
  535. }
  536. FileInfo info = new FileInfo(firmwarePackage);
  537. using (FileStream file = File.OpenRead(firmwarePackage))
  538. {
  539. switch (info.Extension)
  540. {
  541. case ".zip":
  542. using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage))
  543. {
  544. return VerifyAndGetVersionZip(archive);
  545. }
  546. case ".xci":
  547. Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage());
  548. if (xci.HasPartition(XciPartitionType.Update))
  549. {
  550. XciPartition partition = xci.OpenPartition(XciPartitionType.Update);
  551. return VerifyAndGetVersion(partition);
  552. }
  553. else
  554. {
  555. throw new InvalidFirmwarePackageException("Update not found in xci file.");
  556. }
  557. default:
  558. break;
  559. }
  560. }
  561. SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory)
  562. {
  563. return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory));
  564. }
  565. SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
  566. {
  567. IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
  568. SystemVersion systemVersion = null;
  569. foreach (var entry in archive.Entries)
  570. {
  571. if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00"))
  572. {
  573. using (Stream ncaStream = GetZipStream(entry))
  574. {
  575. IStorage storage = ncaStream.AsStorage();
  576. Nca nca = new Nca(_virtualFileSystem.KeySet, storage);
  577. if (updateNcas.ContainsKey(nca.Header.TitleId))
  578. {
  579. updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName));
  580. }
  581. else
  582. {
  583. updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>());
  584. updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName));
  585. }
  586. }
  587. }
  588. }
  589. if (updateNcas.ContainsKey(SystemUpdateTitleId))
  590. {
  591. var ncaEntry = updateNcas[SystemUpdateTitleId];
  592. string metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path;
  593. CnmtContentMetaEntry[] metaEntries = null;
  594. var fileEntry = archive.GetEntry(metaPath);
  595. using (Stream ncaStream = GetZipStream(fileEntry))
  596. {
  597. Nca metaNca = new Nca(_virtualFileSystem.KeySet, ncaStream.AsStorage());
  598. IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
  599. string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
  600. if (fs.OpenFile(out IFile metaFile, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
  601. {
  602. var meta = new Cnmt(metaFile.AsStream());
  603. if (meta.Type == ContentMetaType.SystemUpdate)
  604. {
  605. metaEntries = meta.MetaEntries;
  606. updateNcas.Remove(SystemUpdateTitleId);
  607. };
  608. }
  609. }
  610. if (metaEntries == null)
  611. {
  612. throw new FileNotFoundException("System update title was not found in the firmware package.");
  613. }
  614. if (updateNcas.ContainsKey(SystemVersionTitleId))
  615. {
  616. string versionEntry = updateNcas[SystemVersionTitleId].Find(x => x.type != NcaContentType.Meta).path;
  617. using (Stream ncaStream = GetZipStream(archive.GetEntry(versionEntry)))
  618. {
  619. Nca nca = new Nca(_virtualFileSystem.KeySet, ncaStream.AsStorage());
  620. var romfs = nca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
  621. if (romfs.OpenFile(out IFile systemVersionFile, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
  622. {
  623. systemVersion = new SystemVersion(systemVersionFile.AsStream());
  624. }
  625. }
  626. }
  627. foreach (CnmtContentMetaEntry metaEntry in metaEntries)
  628. {
  629. if (updateNcas.TryGetValue(metaEntry.TitleId, out ncaEntry))
  630. {
  631. metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path;
  632. string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path;
  633. // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it.
  634. // This is a perfect valid case, so we should just ignore the missing content nca and continue.
  635. if (contentPath == null)
  636. {
  637. updateNcas.Remove(metaEntry.TitleId);
  638. continue;
  639. }
  640. ZipArchiveEntry metaZipEntry = archive.GetEntry(metaPath);
  641. ZipArchiveEntry contentZipEntry = archive.GetEntry(contentPath);
  642. using (Stream metaNcaStream = GetZipStream(metaZipEntry))
  643. {
  644. using (Stream contentNcaStream = GetZipStream(contentZipEntry))
  645. {
  646. Nca metaNca = new Nca(_virtualFileSystem.KeySet, metaNcaStream.AsStorage());
  647. IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
  648. string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
  649. if (fs.OpenFile(out IFile metaFile, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
  650. {
  651. var meta = new Cnmt(metaFile.AsStream());
  652. IStorage contentStorage = contentNcaStream.AsStorage();
  653. if (contentStorage.GetSize(out long size).IsSuccess())
  654. {
  655. byte[] contentData = new byte[size];
  656. Span<byte> content = new Span<byte>(contentData);
  657. contentStorage.Read(0, content);
  658. Span<byte> hash = new Span<byte>(new byte[32]);
  659. LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash);
  660. if (LibHac.Util.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash))
  661. {
  662. updateNcas.Remove(metaEntry.TitleId);
  663. }
  664. }
  665. }
  666. }
  667. }
  668. }
  669. }
  670. if (updateNcas.Count > 0)
  671. {
  672. string extraNcas = string.Empty;
  673. foreach (var entry in updateNcas)
  674. {
  675. foreach (var nca in entry.Value)
  676. {
  677. extraNcas += nca.path + Environment.NewLine;
  678. }
  679. }
  680. throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}");
  681. }
  682. }
  683. else
  684. {
  685. throw new FileNotFoundException("System update title was not found in the firmware package.");
  686. }
  687. return systemVersion;
  688. }
  689. SystemVersion VerifyAndGetVersion(IFileSystem filesystem)
  690. {
  691. IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
  692. SystemVersion systemVersion = null;
  693. CnmtContentMetaEntry[] metaEntries = null;
  694. foreach (var entry in filesystem.EnumerateEntries("/", "*.nca"))
  695. {
  696. IStorage ncaStorage = OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage();
  697. Nca nca = new Nca(_virtualFileSystem.KeySet, ncaStorage);
  698. if (nca.Header.TitleId == SystemUpdateTitleId && nca.Header.ContentType == NcaContentType.Meta)
  699. {
  700. IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
  701. string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
  702. if (fs.OpenFile(out IFile metaFile, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
  703. {
  704. var meta = new Cnmt(metaFile.AsStream());
  705. if (meta.Type == ContentMetaType.SystemUpdate)
  706. {
  707. metaEntries = meta.MetaEntries;
  708. }
  709. };
  710. continue;
  711. }
  712. else if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data)
  713. {
  714. var romfs = nca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
  715. if (romfs.OpenFile(out IFile systemVersionFile, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
  716. {
  717. systemVersion = new SystemVersion(systemVersionFile.AsStream());
  718. }
  719. }
  720. if (updateNcas.ContainsKey(nca.Header.TitleId))
  721. {
  722. updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath));
  723. }
  724. else
  725. {
  726. updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>());
  727. updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath));
  728. }
  729. ncaStorage.Dispose();
  730. }
  731. if (metaEntries == null)
  732. {
  733. throw new FileNotFoundException("System update title was not found in the firmware package.");
  734. }
  735. foreach (CnmtContentMetaEntry metaEntry in metaEntries)
  736. {
  737. if (updateNcas.TryGetValue(metaEntry.TitleId, out var ncaEntry))
  738. {
  739. var metaNcaEntry = ncaEntry.Find(x => x.type == NcaContentType.Meta);
  740. string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path;
  741. // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it.
  742. // This is a perfect valid case, so we should just ignore the missing content nca and continue.
  743. if (contentPath == null)
  744. {
  745. updateNcas.Remove(metaEntry.TitleId);
  746. continue;
  747. }
  748. IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaEntry.path, OpenMode.Read).AsStorage();
  749. IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentPath, OpenMode.Read).AsStorage();
  750. Nca metaNca = new Nca(_virtualFileSystem.KeySet, metaStorage);
  751. IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
  752. string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath;
  753. if (fs.OpenFile(out IFile metaFile, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess())
  754. {
  755. var meta = new Cnmt(metaFile.AsStream());
  756. if (contentStorage.GetSize(out long size).IsSuccess())
  757. {
  758. byte[] contentData = new byte[size];
  759. Span<byte> content = new Span<byte>(contentData);
  760. contentStorage.Read(0, content);
  761. Span<byte> hash = new Span<byte>(new byte[32]);
  762. LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash);
  763. if (LibHac.Util.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash))
  764. {
  765. updateNcas.Remove(metaEntry.TitleId);
  766. }
  767. }
  768. }
  769. }
  770. }
  771. if (updateNcas.Count > 0)
  772. {
  773. string extraNcas = string.Empty;
  774. foreach (var entry in updateNcas)
  775. {
  776. foreach (var nca in entry.Value)
  777. {
  778. extraNcas += nca.path + Environment.NewLine;
  779. }
  780. }
  781. throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}");
  782. }
  783. return systemVersion;
  784. }
  785. return null;
  786. }
  787. public SystemVersion GetCurrentFirmwareVersion()
  788. {
  789. IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
  790. LoadEntries();
  791. lock (_lock)
  792. {
  793. var locationEnties = _locationEntries[StorageId.NandSystem];
  794. foreach (var entry in locationEnties)
  795. {
  796. if (entry.ContentType == NcaContentType.Data)
  797. {
  798. var path = _virtualFileSystem.SwitchPathToSystemPath(entry.ContentPath);
  799. using (FileStream fileStream = File.OpenRead(path))
  800. {
  801. Nca nca = new Nca(_virtualFileSystem.KeySet, fileStream.AsStorage());
  802. if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data)
  803. {
  804. var romfs = nca.OpenFileSystem(NcaSectionType.Data, integrityCheckLevel);
  805. if (romfs.OpenFile(out IFile systemVersionFile, "/file".ToU8Span(), OpenMode.Read).IsSuccess())
  806. {
  807. return new SystemVersion(systemVersionFile.AsStream());
  808. }
  809. }
  810. }
  811. }
  812. }
  813. }
  814. return null;
  815. }
  816. }
  817. }