using LibHac.FsSystem; using LibHac.FsSystem.NcaUtils; using Ryujinx.HLE.HOS.Services.Time; using Ryujinx.HLE.Utilities; using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Ryujinx.HLE.FileSystem.Content { internal class ContentManager { private Dictionary> _locationEntries; private Dictionary _sharedFontTitleDictionary; private Dictionary _sharedFontFilenameDictionary; private SortedDictionary<(ulong, NcaContentType), string> _contentDictionary; private Switch _device; public ContentManager(Switch device) { _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); _locationEntries = new Dictionary>(); _sharedFontTitleDictionary = new Dictionary { { "FontStandard", 0x0100000000000811 }, { "FontChineseSimplified", 0x0100000000000814 }, { "FontExtendedChineseSimplified", 0x0100000000000814 }, { "FontKorean", 0x0100000000000812 }, { "FontChineseTraditional", 0x0100000000000813 }, { "FontNintendoExtended", 0x0100000000000810 } }; _sharedFontFilenameDictionary = new Dictionary { { "FontStandard", "nintendo_udsg-r_std_003.bfttf" }, { "FontChineseSimplified", "nintendo_udsg-r_org_zh-cn_003.bfttf" }, { "FontExtendedChineseSimplified", "nintendo_udsg-r_ext_zh-cn_003.bfttf" }, { "FontKorean", "nintendo_udsg-r_ko_003.bfttf" }, { "FontChineseTraditional", "nintendo_udjxh-db_zh-tw_003.bfttf" }, { "FontNintendoExtended", "nintendo_ext_003.bfttf" } }; _device = device; } public void LoadEntries() { _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); foreach (StorageId storageId in Enum.GetValues(typeof(StorageId))) { string contentDirectory = null; string contentPathString = null; string registeredDirectory = null; try { contentPathString = LocationHelper.GetContentRoot(storageId); contentDirectory = LocationHelper.GetRealPath(_device.FileSystem, contentPathString); registeredDirectory = Path.Combine(contentDirectory, "registered"); } catch (NotSupportedException) { continue; } Directory.CreateDirectory(registeredDirectory); LinkedList locationList = new LinkedList(); void AddEntry(LocationEntry entry) { locationList.AddLast(entry); } foreach (string directoryPath in Directory.EnumerateDirectories(registeredDirectory)) { if (Directory.GetFiles(directoryPath).Length > 0) { string ncaName = new DirectoryInfo(directoryPath).Name.Replace(".nca", string.Empty); using (FileStream ncaFile = new FileStream(Directory.GetFiles(directoryPath)[0], FileMode.Open, FileAccess.Read)) { Nca nca = new Nca(_device.System.KeySet, ncaFile.AsStorage()); string switchPath = contentPathString + ":/" + ncaFile.Name.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar); // Change path format to switch's switchPath = switchPath.Replace('\\', '/'); LocationEntry entry = new LocationEntry(switchPath, 0, (long)nca.Header.TitleId, nca.Header.ContentType); AddEntry(entry); _contentDictionary.Add((nca.Header.TitleId, nca.Header.ContentType), ncaName); } } } foreach (string filePath in Directory.EnumerateFiles(contentDirectory)) { if (Path.GetExtension(filePath) == ".nca") { string ncaName = Path.GetFileNameWithoutExtension(filePath); using (FileStream ncaFile = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { Nca nca = new Nca(_device.System.KeySet, ncaFile.AsStorage()); string switchPath = contentPathString + ":/" + filePath.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar); // Change path format to switch's switchPath = switchPath.Replace('\\', '/'); LocationEntry entry = new LocationEntry(switchPath, 0, (long)nca.Header.TitleId, nca.Header.ContentType); AddEntry(entry); _contentDictionary.Add((nca.Header.TitleId, nca.Header.ContentType), ncaName); } } } if (_locationEntries.ContainsKey(storageId) && _locationEntries[storageId]?.Count == 0) { _locationEntries.Remove(storageId); } if (!_locationEntries.ContainsKey(storageId)) { _locationEntries.Add(storageId, locationList); } } TimeManager.Instance.InitializeTimeZone(_device); } public void ClearEntry(long titleId, NcaContentType contentType, StorageId storageId) { RemoveLocationEntry(titleId, contentType, storageId); } public void RefreshEntries(StorageId storageId, int flag) { LinkedList locationList = _locationEntries[storageId]; LinkedListNode locationEntry = locationList.First; while (locationEntry != null) { LinkedListNode nextLocationEntry = locationEntry.Next; if (locationEntry.Value.Flag == flag) { locationList.Remove(locationEntry.Value); } locationEntry = nextLocationEntry; } } public bool HasNca(string ncaId, StorageId storageId) { if (_contentDictionary.ContainsValue(ncaId)) { var content = _contentDictionary.FirstOrDefault(x => x.Value == ncaId); long titleId = (long)content.Key.Item1; NcaContentType contentType = content.Key.Item2; StorageId storage = GetInstalledStorage(titleId, contentType, storageId); return storage == storageId; } return false; } public UInt128 GetInstalledNcaId(long titleId, NcaContentType contentType) { if (_contentDictionary.ContainsKey(((ulong)titleId,contentType))) { return new UInt128(_contentDictionary[((ulong)titleId,contentType)]); } return new UInt128(); } public StorageId GetInstalledStorage(long titleId, NcaContentType contentType, StorageId storageId) { LocationEntry locationEntry = GetLocation(titleId, contentType, storageId); return locationEntry.ContentPath != null ? LocationHelper.GetStorageId(locationEntry.ContentPath) : StorageId.None; } public string GetInstalledContentPath(long titleId, StorageId storageId, NcaContentType contentType) { LocationEntry locationEntry = GetLocation(titleId, contentType, storageId); if (VerifyContentType(locationEntry, contentType)) { return locationEntry.ContentPath; } return string.Empty; } public void RedirectLocation(LocationEntry newEntry, StorageId storageId) { LocationEntry locationEntry = GetLocation(newEntry.TitleId, newEntry.ContentType, storageId); if (locationEntry.ContentPath != null) { RemoveLocationEntry(newEntry.TitleId, newEntry.ContentType, storageId); } AddLocationEntry(newEntry, storageId); } private bool VerifyContentType(LocationEntry locationEntry, NcaContentType contentType) { if (locationEntry.ContentPath == null) { return false; } StorageId storageId = LocationHelper.GetStorageId(locationEntry.ContentPath); string installedPath = _device.FileSystem.SwitchPathToSystemPath(locationEntry.ContentPath); if (!string.IsNullOrWhiteSpace(installedPath)) { if (File.Exists(installedPath)) { using (FileStream file = new FileStream(installedPath, FileMode.Open, FileAccess.Read)) { Nca nca = new Nca(_device.System.KeySet, file.AsStorage()); bool contentCheck = nca.Header.ContentType == contentType; return contentCheck; } } } return false; } private void AddLocationEntry(LocationEntry entry, StorageId storageId) { LinkedList locationList = null; if (_locationEntries.ContainsKey(storageId)) { locationList = _locationEntries[storageId]; } if (locationList != null) { if (locationList.Contains(entry)) { locationList.Remove(entry); } locationList.AddLast(entry); } } private void RemoveLocationEntry(long titleId, NcaContentType contentType, StorageId storageId) { LinkedList locationList = null; if (_locationEntries.ContainsKey(storageId)) { locationList = _locationEntries[storageId]; } if (locationList != null) { LocationEntry entry = locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType); if (entry.ContentPath != null) { locationList.Remove(entry); } } } public bool TryGetFontTitle(string fontName, out long titleId) { return _sharedFontTitleDictionary.TryGetValue(fontName, out titleId); } public bool TryGetFontFilename(string fontName, out string filename) { return _sharedFontFilenameDictionary.TryGetValue(fontName, out filename); } private LocationEntry GetLocation(long titleId, NcaContentType contentType, StorageId storageId) { LinkedList locationList = _locationEntries[storageId]; return locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType); } } }