From e3b36db71c62a34a26b30683dd5ad5410c97cc9c Mon Sep 17 00:00:00 2001 From: Ac_K Date: Tue, 22 Mar 2022 20:46:16 +0100 Subject: hle: Some cleanup (#3210) * hle: Some cleanup This PR cleaned up a bit the HLE folder and the VirtualFileSystem one, since we use LibHac, we can use some class of it directly instead of duplicate things. The "Content" of VFS folder is removed since it should be handled in the NCM service directly. A larger cleanup should be done later since there is still be duplicated code here and there. * Fix Headless.SDL2 * Addresses gdkchan feedback --- Ryujinx.HLE/FileSystem/Content/ContentManager.cs | 1054 -------------------- Ryujinx.HLE/FileSystem/Content/ContentPath.cs | 19 - Ryujinx.HLE/FileSystem/Content/LocationEntry.cs | 25 - Ryujinx.HLE/FileSystem/Content/LocationHelper.cs | 91 -- Ryujinx.HLE/FileSystem/Content/StorageId.cs | 9 - Ryujinx.HLE/FileSystem/Content/SystemVersion.cs | 41 - Ryujinx.HLE/FileSystem/Content/TitleType.cs | 15 - Ryujinx.HLE/FileSystem/ContentManager.cs | 1053 +++++++++++++++++++ Ryujinx.HLE/FileSystem/ContentPath.cs | 82 ++ .../FileSystem/EncryptedFileSystemCreator.cs | 5 +- Ryujinx.HLE/FileSystem/LocationEntry.cs | 25 + Ryujinx.HLE/FileSystem/SaveDataType.cs | 12 - Ryujinx.HLE/FileSystem/SaveInfo.cs | 27 - Ryujinx.HLE/FileSystem/SaveSpaceId.cs | 10 - Ryujinx.HLE/FileSystem/StorageId.cs | 12 - Ryujinx.HLE/FileSystem/SystemVersion.cs | 40 + Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 88 +- Ryujinx.HLE/HLEConfiguration.cs | 1 - Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs | 4 +- Ryujinx.HLE/HOS/Horizon.cs | 5 +- .../HOS/Services/Arp/ApplicationLaunchProperty.cs | 6 +- Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs | 2 - Ryujinx.HLE/HOS/Services/Hid/Hid.cs | 4 +- .../Services/Ncm/Lr/ILocationResolverManager.cs | 2 +- .../LocationResolverManager/ILocationResolver.cs | 2 +- .../Nim/IShopServiceAccessServerInterface.cs | 4 +- .../HOS/Services/Sdb/Pl/SharedFontManager.cs | 4 +- .../HOS/Services/Settings/ISystemSettingsServer.cs | 4 +- Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs | 3 +- .../HOS/Services/Ssl/BuiltInCertificateManager.cs | 4 +- .../Time/TimeZone/TimeZoneContentManager.cs | 4 +- Ryujinx.HLE/Switch.cs | 87 +- Ryujinx.Headless.SDL2/Program.cs | 2 - Ryujinx/Ui/Helper/SetupValidator.cs | 2 +- Ryujinx/Ui/MainWindow.cs | 22 +- Ryujinx/Ui/Windows/AvatarWindow.cs | 4 +- Ryujinx/Ui/Windows/SettingsWindow.cs | 4 +- Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs | 3 +- 38 files changed, 1300 insertions(+), 1481 deletions(-) delete mode 100644 Ryujinx.HLE/FileSystem/Content/ContentManager.cs delete mode 100644 Ryujinx.HLE/FileSystem/Content/ContentPath.cs delete mode 100644 Ryujinx.HLE/FileSystem/Content/LocationEntry.cs delete mode 100644 Ryujinx.HLE/FileSystem/Content/LocationHelper.cs delete mode 100644 Ryujinx.HLE/FileSystem/Content/StorageId.cs delete mode 100644 Ryujinx.HLE/FileSystem/Content/SystemVersion.cs delete mode 100644 Ryujinx.HLE/FileSystem/Content/TitleType.cs create mode 100644 Ryujinx.HLE/FileSystem/ContentManager.cs create mode 100644 Ryujinx.HLE/FileSystem/ContentPath.cs create mode 100644 Ryujinx.HLE/FileSystem/LocationEntry.cs delete mode 100644 Ryujinx.HLE/FileSystem/SaveDataType.cs delete mode 100644 Ryujinx.HLE/FileSystem/SaveInfo.cs delete mode 100644 Ryujinx.HLE/FileSystem/SaveSpaceId.cs delete mode 100644 Ryujinx.HLE/FileSystem/StorageId.cs create mode 100644 Ryujinx.HLE/FileSystem/SystemVersion.cs diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs deleted file mode 100644 index 8ac31a4c..00000000 --- a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs +++ /dev/null @@ -1,1054 +0,0 @@ -using LibHac.Common; -using LibHac.Common.Keys; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSystem; -using LibHac.Ncm; -using LibHac.Tools.Fs; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using LibHac.Tools.Ncm; -using Ryujinx.Common.Logging; -using Ryujinx.HLE.Exceptions; -using Ryujinx.HLE.HOS.Services.Ssl; -using Ryujinx.HLE.HOS.Services.Time; -using Ryujinx.HLE.Utilities; -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using Path = System.IO.Path; - -namespace Ryujinx.HLE.FileSystem.Content -{ - public class ContentManager - { - private const ulong SystemVersionTitleId = 0x0100000000000809; - private const ulong SystemUpdateTitleId = 0x0100000000000816; - - private Dictionary> _locationEntries; - - private Dictionary _sharedFontTitleDictionary; - private Dictionary _systemTitlesNameDictionary; - private Dictionary _sharedFontFilenameDictionary; - - private SortedDictionary<(ulong titleId, NcaContentType type), string> _contentDictionary; - - private struct AocItem - { - public readonly string ContainerPath; - public readonly string NcaPath; - public bool Enabled; - - public AocItem(string containerPath, string ncaPath, bool enabled) - { - ContainerPath = containerPath; - NcaPath = ncaPath; - Enabled = enabled; - } - } - - private SortedList _aocData { get; } - - private VirtualFileSystem _virtualFileSystem; - - private readonly object _lock = new object(); - - public ContentManager(VirtualFileSystem virtualFileSystem) - { - _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); - _locationEntries = new Dictionary>(); - - _sharedFontTitleDictionary = new Dictionary - { - { "FontStandard", 0x0100000000000811 }, - { "FontChineseSimplified", 0x0100000000000814 }, - { "FontExtendedChineseSimplified", 0x0100000000000814 }, - { "FontKorean", 0x0100000000000812 }, - { "FontChineseTraditional", 0x0100000000000813 }, - { "FontNintendoExtended", 0x0100000000000810 } - }; - - _systemTitlesNameDictionary = new Dictionary() - { - { 0x010000000000080E, "TimeZoneBinary" }, - { 0x0100000000000810, "FontNintendoExtension" }, - { 0x0100000000000811, "FontStandard" }, - { 0x0100000000000812, "FontKorean" }, - { 0x0100000000000813, "FontChineseTraditional" }, - { 0x0100000000000814, "FontChineseSimple" }, - }; - - _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" } - }; - - _virtualFileSystem = virtualFileSystem; - - _aocData = new SortedList(); - } - - public void LoadEntries(Switch device = null) - { - lock (_lock) - { - _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); - _locationEntries = new Dictionary>(); - - foreach (StorageId storageId in Enum.GetValues()) - { - string contentDirectory = null; - string contentPathString = null; - string registeredDirectory = null; - - try - { - contentPathString = LocationHelper.GetContentRoot(storageId); - contentDirectory = LocationHelper.GetRealPath(_virtualFileSystem, 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 = File.OpenRead(Directory.GetFiles(directoryPath)[0])) - { - Nca nca = new Nca(_virtualFileSystem.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, - 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(_virtualFileSystem.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, - 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); - } - } - - if (device != null) - { - TimeManager.Instance.InitializeTimeZone(device); - BuiltInCertificateManager.Instance.Initialize(device); - device.System.SharedFontManager.Initialize(); - } - } - } - - // fs must contain AOC nca files in its root - public void AddAocData(IFileSystem fs, string containerPath, ulong aocBaseId, IntegrityCheckLevel integrityCheckLevel) - { - _virtualFileSystem.ImportTickets(fs); - - foreach (var ncaPath in fs.EnumerateEntries("*.cnmt.nca", SearchOptions.Default)) - { - using var ncaFile = new UniqueRef(); - - fs.OpenFile(ref ncaFile.Ref(), ncaPath.FullPath.ToU8Span(), OpenMode.Read); - var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()); - if (nca.Header.ContentType != NcaContentType.Meta) - { - Logger.Warning?.Print(LogClass.Application, $"{ncaPath} is not a valid metadata file"); - - continue; - } - - using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel); - using var cnmtFile = new UniqueRef(); - - pfs0.OpenFile(ref cnmtFile.Ref(), pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read); - - var cnmt = new Cnmt(cnmtFile.Get.AsStream()); - - if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId) - { - continue; - } - - string ncaId = BitConverter.ToString(cnmt.ContentEntries[0].NcaId).Replace("-", "").ToLower(); - if (!_aocData.TryAdd(cnmt.TitleId, new AocItem(containerPath, $"{ncaId}.nca", true))) - { - Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {cnmt.TitleId:X16}"); - } - else - { - Logger.Info?.Print(LogClass.Application, $"Found AddOnContent with TitleId {cnmt.TitleId:X16}"); - } - } - } - - public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool enabled) - { - if (!_aocData.TryAdd(titleId, new AocItem(containerPath, ncaPath, enabled))) - { - Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {titleId:X16}"); - } - else - { - Logger.Info?.Print(LogClass.Application, $"Found AddOnContent with TitleId {titleId:X16}"); - - using (FileStream fileStream = File.OpenRead(containerPath)) - using (PartitionFileSystem pfs = new PartitionFileSystem(fileStream.AsStorage())) - { - _virtualFileSystem.ImportTickets(pfs); - } - } - } - - public void ClearAocData() => _aocData.Clear(); - - public int GetAocCount() => _aocData.Where(e => e.Value.Enabled).Count(); - - public IList GetAocTitleIds() => _aocData.Where(e => e.Value.Enabled).Select(e => e.Key).ToList(); - - public bool GetAocDataStorage(ulong aocTitleId, out IStorage aocStorage, IntegrityCheckLevel integrityCheckLevel) - { - aocStorage = null; - - if (_aocData.TryGetValue(aocTitleId, out AocItem aoc) && aoc.Enabled) - { - var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read); - using var ncaFile = new UniqueRef(); - PartitionFileSystem pfs; - - switch (Path.GetExtension(aoc.ContainerPath)) - { - case ".xci": - pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure); - pfs.OpenFile(ref ncaFile.Ref(), aoc.NcaPath.ToU8Span(), OpenMode.Read); - break; - case ".nsp": - pfs = new PartitionFileSystem(file.AsStorage()); - pfs.OpenFile(ref ncaFile.Ref(), aoc.NcaPath.ToU8Span(), OpenMode.Read); - break; - default: - return false; // Print error? - } - - aocStorage = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()).OpenStorage(NcaSectionType.Data, integrityCheckLevel); - - return true; - } - - return false; - } - - public void ClearEntry(ulong titleId, NcaContentType contentType, StorageId storageId) - { - lock (_lock) - { - RemoveLocationEntry(titleId, contentType, storageId); - } - } - - public void RefreshEntries(StorageId storageId, int flag) - { - lock (_lock) - { - 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) - { - lock (_lock) - { - if (_contentDictionary.ContainsValue(ncaId)) - { - var content = _contentDictionary.FirstOrDefault(x => x.Value == ncaId); - ulong titleId = content.Key.Item1; - - NcaContentType contentType = content.Key.type; - StorageId storage = GetInstalledStorage(titleId, contentType, storageId); - - return storage == storageId; - } - } - - return false; - } - - public UInt128 GetInstalledNcaId(ulong titleId, NcaContentType contentType) - { - lock (_lock) - { - if (_contentDictionary.ContainsKey((titleId, contentType))) - { - return new UInt128(_contentDictionary[(titleId, contentType)]); - } - } - - return new UInt128(); - } - - public StorageId GetInstalledStorage(ulong titleId, NcaContentType contentType, StorageId storageId) - { - lock (_lock) - { - LocationEntry locationEntry = GetLocation(titleId, contentType, storageId); - - return locationEntry.ContentPath != null ? - LocationHelper.GetStorageId(locationEntry.ContentPath) : StorageId.None; - } - } - - public string GetInstalledContentPath(ulong titleId, StorageId storageId, NcaContentType contentType) - { - lock (_lock) - { - LocationEntry locationEntry = GetLocation(titleId, contentType, storageId); - - if (VerifyContentType(locationEntry, contentType)) - { - return locationEntry.ContentPath; - } - } - - return string.Empty; - } - - public void RedirectLocation(LocationEntry newEntry, StorageId storageId) - { - lock (_lock) - { - 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; - } - - string installedPath = _virtualFileSystem.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(_virtualFileSystem.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(ulong 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 ulong titleId) - { - return _sharedFontTitleDictionary.TryGetValue(fontName, out titleId); - } - - public bool TryGetFontFilename(string fontName, out string filename) - { - return _sharedFontFilenameDictionary.TryGetValue(fontName, out filename); - } - - public bool TryGetSystemTitlesName(ulong titleId, out string name) - { - return _systemTitlesNameDictionary.TryGetValue(titleId, out name); - } - - private LocationEntry GetLocation(ulong titleId, NcaContentType contentType, StorageId storageId) - { - LinkedList locationList = _locationEntries[storageId]; - - return locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType); - } - - public void InstallFirmware(string firmwareSource) - { - string contentPathString = LocationHelper.GetContentRoot(StorageId.NandSystem); - string contentDirectory = LocationHelper.GetRealPath(_virtualFileSystem, contentPathString); - string registeredDirectory = Path.Combine(contentDirectory, "registered"); - string temporaryDirectory = Path.Combine(contentDirectory, "temp"); - - if (Directory.Exists(temporaryDirectory)) - { - Directory.Delete(temporaryDirectory, true); - } - - if (Directory.Exists(firmwareSource)) - { - InstallFromDirectory(firmwareSource, temporaryDirectory); - FinishInstallation(temporaryDirectory, registeredDirectory); - - return; - } - - if (!File.Exists(firmwareSource)) - { - throw new FileNotFoundException("Firmware file does not exist."); - } - - FileInfo info = new FileInfo(firmwareSource); - - using (FileStream file = File.OpenRead(firmwareSource)) - { - switch (info.Extension) - { - case ".zip": - using (ZipArchive archive = ZipFile.OpenRead(firmwareSource)) - { - InstallFromZip(archive, temporaryDirectory); - } - break; - case ".xci": - Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()); - InstallFromCart(xci, temporaryDirectory); - break; - default: - throw new InvalidFirmwarePackageException("Input file is not a valid firmware package"); - } - - FinishInstallation(temporaryDirectory, registeredDirectory); - } - } - - private void FinishInstallation(string temporaryDirectory, string registeredDirectory) - { - if (Directory.Exists(registeredDirectory)) - { - new DirectoryInfo(registeredDirectory).Delete(true); - } - - Directory.Move(temporaryDirectory, registeredDirectory); - - LoadEntries(); - } - - private void InstallFromDirectory(string firmwareDirectory, string temporaryDirectory) - { - InstallFromPartition(new LocalFileSystem(firmwareDirectory), temporaryDirectory); - } - - private void InstallFromPartition(IFileSystem filesystem, string temporaryDirectory) - { - foreach (var entry in filesystem.EnumerateEntries("/", "*.nca")) - { - Nca nca = new Nca(_virtualFileSystem.KeySet, OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage()); - - SaveNca(nca, entry.Name.Remove(entry.Name.IndexOf('.')), temporaryDirectory); - } - } - - private void InstallFromCart(Xci gameCard, string temporaryDirectory) - { - if (gameCard.HasPartition(XciPartitionType.Update)) - { - XciPartition partition = gameCard.OpenPartition(XciPartitionType.Update); - - InstallFromPartition(partition, temporaryDirectory); - } - else - { - throw new Exception("Update not found in xci file."); - } - } - - private void InstallFromZip(ZipArchive archive, string temporaryDirectory) - { - using (archive) - { - foreach (var entry in archive.Entries) - { - if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00")) - { - // Clean up the name and get the NcaId - - string[] pathComponents = entry.FullName.Replace(".cnmt", "").Split('/'); - - string ncaId = pathComponents[pathComponents.Length - 1]; - - // If this is a fragmented nca, we need to get the previous element.GetZip - if (ncaId.Equals("00")) - { - ncaId = pathComponents[pathComponents.Length - 2]; - } - - if (ncaId.Contains(".nca")) - { - string newPath = Path.Combine(temporaryDirectory, ncaId); - - Directory.CreateDirectory(newPath); - - entry.ExtractToFile(Path.Combine(newPath, "00")); - } - } - } - } - } - - public void SaveNca(Nca nca, string ncaId, string temporaryDirectory) - { - string newPath = Path.Combine(temporaryDirectory, ncaId + ".nca"); - - Directory.CreateDirectory(newPath); - - using (FileStream file = File.Create(Path.Combine(newPath, "00"))) - { - nca.BaseStorage.AsStream().CopyTo(file); - } - } - - private IFile OpenPossibleFragmentedFile(IFileSystem filesystem, string path, OpenMode mode) - { - using var file = new UniqueRef(); - - if (filesystem.FileExists($"{path}/00")) - { - filesystem.OpenFile(ref file.Ref(), $"{path}/00".ToU8Span(), mode); - } - else - { - filesystem.OpenFile(ref file.Ref(), path.ToU8Span(), mode); - } - - return file.Release(); - } - - private Stream GetZipStream(ZipArchiveEntry entry) - { - MemoryStream dest = new MemoryStream(); - - Stream src = entry.Open(); - - src.CopyTo(dest); - src.Dispose(); - - return dest; - } - - public SystemVersion VerifyFirmwarePackage(string firmwarePackage) - { - _virtualFileSystem.ReloadKeySet(); - - // LibHac.NcaHeader's DecryptHeader doesn't check if HeaderKey is empty and throws InvalidDataException instead - // So, we check it early for a better user experience. - if (_virtualFileSystem.KeySet.HeaderKey.IsZeros()) - { - throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers."); - } - - Dictionary> updateNcas = new Dictionary>(); - - if (Directory.Exists(firmwarePackage)) - { - return VerifyAndGetVersionDirectory(firmwarePackage); - } - - if (!File.Exists(firmwarePackage)) - { - throw new FileNotFoundException("Firmware file does not exist."); - } - - FileInfo info = new FileInfo(firmwarePackage); - - using (FileStream file = File.OpenRead(firmwarePackage)) - { - switch (info.Extension) - { - case ".zip": - using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage)) - { - return VerifyAndGetVersionZip(archive); - } - case ".xci": - Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()); - - if (xci.HasPartition(XciPartitionType.Update)) - { - XciPartition partition = xci.OpenPartition(XciPartitionType.Update); - - return VerifyAndGetVersion(partition); - } - else - { - throw new InvalidFirmwarePackageException("Update not found in xci file."); - } - default: - break; - } - } - - SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory) - { - return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory)); - } - - SystemVersion VerifyAndGetVersionZip(ZipArchive archive) - { - SystemVersion systemVersion = null; - - foreach (var entry in archive.Entries) - { - if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00")) - { - using (Stream ncaStream = GetZipStream(entry)) - { - IStorage storage = ncaStream.AsStorage(); - - Nca nca = new Nca(_virtualFileSystem.KeySet, storage); - - if (updateNcas.ContainsKey(nca.Header.TitleId)) - { - updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); - } - else - { - updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>()); - updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); - } - } - } - } - - if (updateNcas.ContainsKey(SystemUpdateTitleId)) - { - var ncaEntry = updateNcas[SystemUpdateTitleId]; - - string metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path; - - CnmtContentMetaEntry[] metaEntries = null; - - var fileEntry = archive.GetEntry(metaPath); - - using (Stream ncaStream = GetZipStream(fileEntry)) - { - Nca metaNca = new Nca(_virtualFileSystem.KeySet, ncaStream.AsStorage()); - - IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - - string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; - - using var metaFile = new UniqueRef(); - - if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) - { - var meta = new Cnmt(metaFile.Get.AsStream()); - - if (meta.Type == ContentMetaType.SystemUpdate) - { - metaEntries = meta.MetaEntries; - - updateNcas.Remove(SystemUpdateTitleId); - }; - } - } - - if (metaEntries == null) - { - throw new FileNotFoundException("System update title was not found in the firmware package."); - } - - if (updateNcas.ContainsKey(SystemVersionTitleId)) - { - string versionEntry = updateNcas[SystemVersionTitleId].Find(x => x.type != NcaContentType.Meta).path; - - using (Stream ncaStream = GetZipStream(archive.GetEntry(versionEntry))) - { - Nca nca = new Nca(_virtualFileSystem.KeySet, ncaStream.AsStorage()); - - var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - - using var systemVersionFile = new UniqueRef(); - - if (romfs.OpenFile(ref systemVersionFile.Ref(), "/file".ToU8Span(), OpenMode.Read).IsSuccess()) - { - systemVersion = new SystemVersion(systemVersionFile.Get.AsStream()); - } - } - } - - foreach (CnmtContentMetaEntry metaEntry in metaEntries) - { - if (updateNcas.TryGetValue(metaEntry.TitleId, out ncaEntry)) - { - metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path; - - string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path; - - // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it. - // This is a perfect valid case, so we should just ignore the missing content nca and continue. - if (contentPath == null) - { - updateNcas.Remove(metaEntry.TitleId); - - continue; - } - - ZipArchiveEntry metaZipEntry = archive.GetEntry(metaPath); - ZipArchiveEntry contentZipEntry = archive.GetEntry(contentPath); - - using (Stream metaNcaStream = GetZipStream(metaZipEntry)) - { - using (Stream contentNcaStream = GetZipStream(contentZipEntry)) - { - Nca metaNca = new Nca(_virtualFileSystem.KeySet, metaNcaStream.AsStorage()); - - IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - - string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; - - using var metaFile = new UniqueRef(); - - if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) - { - var meta = new Cnmt(metaFile.Get.AsStream()); - - IStorage contentStorage = contentNcaStream.AsStorage(); - if (contentStorage.GetSize(out long size).IsSuccess()) - { - byte[] contentData = new byte[size]; - - Span content = new Span(contentData); - - contentStorage.Read(0, content); - - Span hash = new Span(new byte[32]); - - LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash); - - if (LibHac.Common.Utilities.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash)) - { - updateNcas.Remove(metaEntry.TitleId); - } - } - } - } - } - } - } - - if (updateNcas.Count > 0) - { - string extraNcas = string.Empty; - - foreach (var entry in updateNcas) - { - foreach (var nca in entry.Value) - { - extraNcas += nca.path + Environment.NewLine; - } - } - - throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}"); - } - } - else - { - throw new FileNotFoundException("System update title was not found in the firmware package."); - } - - return systemVersion; - } - - SystemVersion VerifyAndGetVersion(IFileSystem filesystem) - { - SystemVersion systemVersion = null; - - CnmtContentMetaEntry[] metaEntries = null; - - foreach (var entry in filesystem.EnumerateEntries("/", "*.nca")) - { - IStorage ncaStorage = OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage(); - - Nca nca = new Nca(_virtualFileSystem.KeySet, ncaStorage); - - if (nca.Header.TitleId == SystemUpdateTitleId && nca.Header.ContentType == NcaContentType.Meta) - { - IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - - string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; - - using var metaFile = new UniqueRef(); - - if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) - { - var meta = new Cnmt(metaFile.Get.AsStream()); - - if (meta.Type == ContentMetaType.SystemUpdate) - { - metaEntries = meta.MetaEntries; - } - }; - - continue; - } - else if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) - { - var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - - using var systemVersionFile = new UniqueRef(); - - if (romfs.OpenFile(ref systemVersionFile.Ref(), "/file".ToU8Span(), OpenMode.Read).IsSuccess()) - { - systemVersion = new SystemVersion(systemVersionFile.Get.AsStream()); - } - } - - if (updateNcas.ContainsKey(nca.Header.TitleId)) - { - updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath)); - } - else - { - updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>()); - updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath)); - } - - ncaStorage.Dispose(); - } - - if (metaEntries == null) - { - throw new FileNotFoundException("System update title was not found in the firmware package."); - } - - foreach (CnmtContentMetaEntry metaEntry in metaEntries) - { - if (updateNcas.TryGetValue(metaEntry.TitleId, out var ncaEntry)) - { - var metaNcaEntry = ncaEntry.Find(x => x.type == NcaContentType.Meta); - string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path; - - // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it. - // This is a perfect valid case, so we should just ignore the missing content nca and continue. - if (contentPath == null) - { - updateNcas.Remove(metaEntry.TitleId); - - continue; - } - - IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaEntry.path, OpenMode.Read).AsStorage(); - IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentPath, OpenMode.Read).AsStorage(); - - Nca metaNca = new Nca(_virtualFileSystem.KeySet, metaStorage); - - IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - - string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; - - using var metaFile = new UniqueRef(); - - if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) - { - var meta = new Cnmt(metaFile.Get.AsStream()); - - if (contentStorage.GetSize(out long size).IsSuccess()) - { - byte[] contentData = new byte[size]; - - Span content = new Span(contentData); - - contentStorage.Read(0, content); - - Span hash = new Span(new byte[32]); - - LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash); - - if (LibHac.Common.Utilities.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash)) - { - updateNcas.Remove(metaEntry.TitleId); - } - } - } - } - } - - if (updateNcas.Count > 0) - { - string extraNcas = string.Empty; - - foreach (var entry in updateNcas) - { - foreach (var nca in entry.Value) - { - extraNcas += nca.path + Environment.NewLine; - } - } - - throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}"); - } - - return systemVersion; - } - - return null; - } - - public SystemVersion GetCurrentFirmwareVersion() - { - LoadEntries(); - - lock (_lock) - { - var locationEnties = _locationEntries[StorageId.NandSystem]; - - foreach (var entry in locationEnties) - { - if (entry.ContentType == NcaContentType.Data) - { - var path = _virtualFileSystem.SwitchPathToSystemPath(entry.ContentPath); - - using (FileStream fileStream = File.OpenRead(path)) - { - Nca nca = new Nca(_virtualFileSystem.KeySet, fileStream.AsStorage()); - - if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) - { - var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); - - using var systemVersionFile = new UniqueRef(); - - if (romfs.OpenFile(ref systemVersionFile.Ref(), "/file".ToU8Span(), OpenMode.Read).IsSuccess()) - { - return new SystemVersion(systemVersionFile.Get.AsStream()); - } - } - - } - } - } - } - - return null; - } - } -} diff --git a/Ryujinx.HLE/FileSystem/Content/ContentPath.cs b/Ryujinx.HLE/FileSystem/Content/ContentPath.cs deleted file mode 100644 index 1e2c8ab3..00000000 --- a/Ryujinx.HLE/FileSystem/Content/ContentPath.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.HLE.FileSystem.Content -{ - static class ContentPath - { - public const string SystemContent = "@SystemContent"; - public const string UserContent = "@UserContent"; - public const string SdCardContent = "@SdCardContent"; - public const string SdCard = "@SdCard"; - public const string CalibFile = "@CalibFile"; - public const string Safe = "@Safe"; - public const string User = "@User"; - public const string System = "@System"; - public const string Host = "@Host"; - public const string GamecardApp = "@GcApp"; - public const string GamecardContents = "@GcS00000001"; - public const string GamecardUpdate = "@upp"; - public const string RegisteredUpdate = "@RegUpdate"; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/Content/LocationEntry.cs b/Ryujinx.HLE/FileSystem/Content/LocationEntry.cs deleted file mode 100644 index bf938746..00000000 --- a/Ryujinx.HLE/FileSystem/Content/LocationEntry.cs +++ /dev/null @@ -1,25 +0,0 @@ -using LibHac.FsSystem; - -namespace Ryujinx.HLE.FileSystem.Content -{ - public struct LocationEntry - { - public string ContentPath { get; private set; } - public int Flag { get; private set; } - public ulong TitleId { get; private set; } - public NcaContentType ContentType { get; private set; } - - public LocationEntry(string contentPath, int flag, ulong titleId, NcaContentType contentType) - { - ContentPath = contentPath; - Flag = flag; - TitleId = titleId; - ContentType = contentType; - } - - public void SetFlag(int flag) - { - Flag = flag; - } - } -} diff --git a/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs b/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs deleted file mode 100644 index c522b053..00000000 --- a/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.IO; - -using static Ryujinx.HLE.FileSystem.VirtualFileSystem; - -namespace Ryujinx.HLE.FileSystem.Content -{ - internal static class LocationHelper - { - public static string GetRealPath(VirtualFileSystem fileSystem, string switchContentPath) - { - string basePath = fileSystem.GetBasePath(); - - switch (switchContentPath) - { - case ContentPath.SystemContent: - return Path.Combine(basePath, SystemNandPath, "Contents"); - case ContentPath.UserContent: - return Path.Combine(basePath, UserNandPath, "Contents"); - case ContentPath.SdCardContent: - return Path.Combine(fileSystem.GetSdCardPath(), "Nintendo", "Contents"); - case ContentPath.System: - return Path.Combine(basePath, SystemNandPath); - case ContentPath.User: - return Path.Combine(basePath, UserNandPath); - default: - throw new NotSupportedException($"Content Path `{switchContentPath}` is not supported."); - } - } - - public static string GetContentPath(ContentStorageId contentStorageId) - { - switch (contentStorageId) - { - case ContentStorageId.NandSystem: - return ContentPath.SystemContent; - case ContentStorageId.NandUser: - return ContentPath.UserContent; - case ContentStorageId.SdCard: - return ContentPath.SdCardContent; - default: - throw new NotSupportedException($"Content Storage `{contentStorageId}` is not supported."); - } - } - - public static string GetContentRoot(StorageId storageId) - { - switch (storageId) - { - case StorageId.NandSystem: - return ContentPath.SystemContent; - case StorageId.NandUser: - return ContentPath.UserContent; - case StorageId.SdCard: - return ContentPath.SdCardContent; - default: - throw new NotSupportedException($"Storage Id `{storageId}` is not supported."); - } - } - - public static StorageId GetStorageId(string contentPathString) - { - string cleanedPath = contentPathString.Split(':')[0]; - - switch (cleanedPath) - { - case ContentPath.SystemContent: - case ContentPath.System: - return StorageId.NandSystem; - - case ContentPath.UserContent: - case ContentPath.User: - return StorageId.NandUser; - - case ContentPath.SdCardContent: - return StorageId.SdCard; - - case ContentPath.Host: - return StorageId.Host; - - case ContentPath.GamecardApp: - case ContentPath.GamecardContents: - case ContentPath.GamecardUpdate: - return StorageId.GameCard; - - default: - return StorageId.None; - } - } - } -} diff --git a/Ryujinx.HLE/FileSystem/Content/StorageId.cs b/Ryujinx.HLE/FileSystem/Content/StorageId.cs deleted file mode 100644 index 4ff3dd65..00000000 --- a/Ryujinx.HLE/FileSystem/Content/StorageId.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.FileSystem.Content -{ - public enum ContentStorageId - { - NandSystem, - NandUser, - SdCard - } -} diff --git a/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs b/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs deleted file mode 100644 index 3f19e135..00000000 --- a/Ryujinx.HLE/FileSystem/Content/SystemVersion.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Ryujinx.HLE.Utilities; -using System.IO; -using System.Text; - -namespace Ryujinx.HLE.FileSystem.Content -{ - public class SystemVersion - { - public byte Major { get; } - public byte Minor { get; } - public byte Micro { get; } - public byte RevisionMajor { get; } - public byte RevisionMinor { get; } - public string PlatformString { get; } - public string Hex { get; } - public string VersionString { get; } - public string VersionTitle { get; } - - public SystemVersion(Stream systemVersionFile) - { - using (BinaryReader reader = new BinaryReader(systemVersionFile)) - { - Major = reader.ReadByte(); - Minor = reader.ReadByte(); - Micro = reader.ReadByte(); - - reader.ReadByte(); // Padding - - RevisionMajor = reader.ReadByte(); - RevisionMinor = reader.ReadByte(); - - reader.ReadBytes(2); // Padding - - PlatformString = StringUtils.ReadInlinedAsciiString(reader, 0x20); - Hex = StringUtils.ReadInlinedAsciiString(reader, 0x40); - VersionString = StringUtils.ReadInlinedAsciiString(reader, 0x18); - VersionTitle = StringUtils.ReadInlinedAsciiString(reader, 0x80); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/Content/TitleType.cs b/Ryujinx.HLE/FileSystem/Content/TitleType.cs deleted file mode 100644 index 6ad26c9c..00000000 --- a/Ryujinx.HLE/FileSystem/Content/TitleType.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.HLE.FileSystem.Content -{ - enum TitleType - { - SystemPrograms = 0x01, - SystemDataArchive = 0x02, - SystemUpdate = 0x03, - FirmwarePackageA = 0x04, - FirmwarePackageB = 0x05, - RegularApplication = 0x80, - Update = 0x81, - AddOnContent = 0x82, - DeltaTitle = 0x83 - } -} diff --git a/Ryujinx.HLE/FileSystem/ContentManager.cs b/Ryujinx.HLE/FileSystem/ContentManager.cs new file mode 100644 index 00000000..2544cdda --- /dev/null +++ b/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -0,0 +1,1053 @@ +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using LibHac.Tools.Ncm; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Ssl; +using Ryujinx.HLE.HOS.Services.Time; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.FileSystem +{ + public class ContentManager + { + private const ulong SystemVersionTitleId = 0x0100000000000809; + private const ulong SystemUpdateTitleId = 0x0100000000000816; + + private Dictionary> _locationEntries; + + private Dictionary _sharedFontTitleDictionary; + private Dictionary _systemTitlesNameDictionary; + private Dictionary _sharedFontFilenameDictionary; + + private SortedDictionary<(ulong titleId, NcaContentType type), string> _contentDictionary; + + private struct AocItem + { + public readonly string ContainerPath; + public readonly string NcaPath; + public bool Enabled; + + public AocItem(string containerPath, string ncaPath, bool enabled) + { + ContainerPath = containerPath; + NcaPath = ncaPath; + Enabled = enabled; + } + } + + private SortedList _aocData { get; } + + private VirtualFileSystem _virtualFileSystem; + + private readonly object _lock = new object(); + + public ContentManager(VirtualFileSystem virtualFileSystem) + { + _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); + _locationEntries = new Dictionary>(); + + _sharedFontTitleDictionary = new Dictionary + { + { "FontStandard", 0x0100000000000811 }, + { "FontChineseSimplified", 0x0100000000000814 }, + { "FontExtendedChineseSimplified", 0x0100000000000814 }, + { "FontKorean", 0x0100000000000812 }, + { "FontChineseTraditional", 0x0100000000000813 }, + { "FontNintendoExtended", 0x0100000000000810 } + }; + + _systemTitlesNameDictionary = new Dictionary() + { + { 0x010000000000080E, "TimeZoneBinary" }, + { 0x0100000000000810, "FontNintendoExtension" }, + { 0x0100000000000811, "FontStandard" }, + { 0x0100000000000812, "FontKorean" }, + { 0x0100000000000813, "FontChineseTraditional" }, + { 0x0100000000000814, "FontChineseSimple" }, + }; + + _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" } + }; + + _virtualFileSystem = virtualFileSystem; + + _aocData = new SortedList(); + } + + public void LoadEntries(Switch device = null) + { + lock (_lock) + { + _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); + _locationEntries = new Dictionary>(); + + foreach (StorageId storageId in Enum.GetValues()) + { + string contentDirectory = null; + string contentPathString = null; + string registeredDirectory = null; + + try + { + contentPathString = ContentPath.GetContentPath(storageId); + contentDirectory = ContentPath.GetRealPath(_virtualFileSystem, 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 = File.OpenRead(Directory.GetFiles(directoryPath)[0])) + { + Nca nca = new Nca(_virtualFileSystem.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, + 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(_virtualFileSystem.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, + 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); + } + } + + if (device != null) + { + TimeManager.Instance.InitializeTimeZone(device); + BuiltInCertificateManager.Instance.Initialize(device); + device.System.SharedFontManager.Initialize(); + } + } + } + + // fs must contain AOC nca files in its root + public void AddAocData(IFileSystem fs, string containerPath, ulong aocBaseId, IntegrityCheckLevel integrityCheckLevel) + { + _virtualFileSystem.ImportTickets(fs); + + foreach (var ncaPath in fs.EnumerateEntries("*.cnmt.nca", SearchOptions.Default)) + { + using var ncaFile = new UniqueRef(); + + fs.OpenFile(ref ncaFile.Ref(), ncaPath.FullPath.ToU8Span(), OpenMode.Read); + var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()); + if (nca.Header.ContentType != NcaContentType.Meta) + { + Logger.Warning?.Print(LogClass.Application, $"{ncaPath} is not a valid metadata file"); + + continue; + } + + using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel); + using var cnmtFile = new UniqueRef(); + + pfs0.OpenFile(ref cnmtFile.Ref(), pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read); + + var cnmt = new Cnmt(cnmtFile.Get.AsStream()); + + if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId) + { + continue; + } + + string ncaId = BitConverter.ToString(cnmt.ContentEntries[0].NcaId).Replace("-", "").ToLower(); + if (!_aocData.TryAdd(cnmt.TitleId, new AocItem(containerPath, $"{ncaId}.nca", true))) + { + Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {cnmt.TitleId:X16}"); + } + else + { + Logger.Info?.Print(LogClass.Application, $"Found AddOnContent with TitleId {cnmt.TitleId:X16}"); + } + } + } + + public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool enabled) + { + if (!_aocData.TryAdd(titleId, new AocItem(containerPath, ncaPath, enabled))) + { + Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {titleId:X16}"); + } + else + { + Logger.Info?.Print(LogClass.Application, $"Found AddOnContent with TitleId {titleId:X16}"); + + using (FileStream fileStream = File.OpenRead(containerPath)) + using (PartitionFileSystem pfs = new PartitionFileSystem(fileStream.AsStorage())) + { + _virtualFileSystem.ImportTickets(pfs); + } + } + } + + public void ClearAocData() => _aocData.Clear(); + + public int GetAocCount() => _aocData.Where(e => e.Value.Enabled).Count(); + + public IList GetAocTitleIds() => _aocData.Where(e => e.Value.Enabled).Select(e => e.Key).ToList(); + + public bool GetAocDataStorage(ulong aocTitleId, out IStorage aocStorage, IntegrityCheckLevel integrityCheckLevel) + { + aocStorage = null; + + if (_aocData.TryGetValue(aocTitleId, out AocItem aoc) && aoc.Enabled) + { + var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read); + using var ncaFile = new UniqueRef(); + PartitionFileSystem pfs; + + switch (Path.GetExtension(aoc.ContainerPath)) + { + case ".xci": + pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure); + pfs.OpenFile(ref ncaFile.Ref(), aoc.NcaPath.ToU8Span(), OpenMode.Read); + break; + case ".nsp": + pfs = new PartitionFileSystem(file.AsStorage()); + pfs.OpenFile(ref ncaFile.Ref(), aoc.NcaPath.ToU8Span(), OpenMode.Read); + break; + default: + return false; // Print error? + } + + aocStorage = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()).OpenStorage(NcaSectionType.Data, integrityCheckLevel); + + return true; + } + + return false; + } + + public void ClearEntry(ulong titleId, NcaContentType contentType, StorageId storageId) + { + lock (_lock) + { + RemoveLocationEntry(titleId, contentType, storageId); + } + } + + public void RefreshEntries(StorageId storageId, int flag) + { + lock (_lock) + { + 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) + { + lock (_lock) + { + if (_contentDictionary.ContainsValue(ncaId)) + { + var content = _contentDictionary.FirstOrDefault(x => x.Value == ncaId); + ulong titleId = content.Key.Item1; + + NcaContentType contentType = content.Key.type; + StorageId storage = GetInstalledStorage(titleId, contentType, storageId); + + return storage == storageId; + } + } + + return false; + } + + public UInt128 GetInstalledNcaId(ulong titleId, NcaContentType contentType) + { + lock (_lock) + { + if (_contentDictionary.ContainsKey((titleId, contentType))) + { + return new UInt128(_contentDictionary[(titleId, contentType)]); + } + } + + return new UInt128(); + } + + public StorageId GetInstalledStorage(ulong titleId, NcaContentType contentType, StorageId storageId) + { + lock (_lock) + { + LocationEntry locationEntry = GetLocation(titleId, contentType, storageId); + + return locationEntry.ContentPath != null ? ContentPath.GetStorageId(locationEntry.ContentPath) : StorageId.None; + } + } + + public string GetInstalledContentPath(ulong titleId, StorageId storageId, NcaContentType contentType) + { + lock (_lock) + { + LocationEntry locationEntry = GetLocation(titleId, contentType, storageId); + + if (VerifyContentType(locationEntry, contentType)) + { + return locationEntry.ContentPath; + } + } + + return string.Empty; + } + + public void RedirectLocation(LocationEntry newEntry, StorageId storageId) + { + lock (_lock) + { + 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; + } + + string installedPath = _virtualFileSystem.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(_virtualFileSystem.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(ulong 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 ulong titleId) + { + return _sharedFontTitleDictionary.TryGetValue(fontName, out titleId); + } + + public bool TryGetFontFilename(string fontName, out string filename) + { + return _sharedFontFilenameDictionary.TryGetValue(fontName, out filename); + } + + public bool TryGetSystemTitlesName(ulong titleId, out string name) + { + return _systemTitlesNameDictionary.TryGetValue(titleId, out name); + } + + private LocationEntry GetLocation(ulong titleId, NcaContentType contentType, StorageId storageId) + { + LinkedList locationList = _locationEntries[storageId]; + + return locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType); + } + + public void InstallFirmware(string firmwareSource) + { + string contentPathString = ContentPath.GetContentPath(StorageId.BuiltInSystem); + string contentDirectory = ContentPath.GetRealPath(_virtualFileSystem, contentPathString); + string registeredDirectory = Path.Combine(contentDirectory, "registered"); + string temporaryDirectory = Path.Combine(contentDirectory, "temp"); + + if (Directory.Exists(temporaryDirectory)) + { + Directory.Delete(temporaryDirectory, true); + } + + if (Directory.Exists(firmwareSource)) + { + InstallFromDirectory(firmwareSource, temporaryDirectory); + FinishInstallation(temporaryDirectory, registeredDirectory); + + return; + } + + if (!File.Exists(firmwareSource)) + { + throw new FileNotFoundException("Firmware file does not exist."); + } + + FileInfo info = new FileInfo(firmwareSource); + + using (FileStream file = File.OpenRead(firmwareSource)) + { + switch (info.Extension) + { + case ".zip": + using (ZipArchive archive = ZipFile.OpenRead(firmwareSource)) + { + InstallFromZip(archive, temporaryDirectory); + } + break; + case ".xci": + Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()); + InstallFromCart(xci, temporaryDirectory); + break; + default: + throw new InvalidFirmwarePackageException("Input file is not a valid firmware package"); + } + + FinishInstallation(temporaryDirectory, registeredDirectory); + } + } + + private void FinishInstallation(string temporaryDirectory, string registeredDirectory) + { + if (Directory.Exists(registeredDirectory)) + { + new DirectoryInfo(registeredDirectory).Delete(true); + } + + Directory.Move(temporaryDirectory, registeredDirectory); + + LoadEntries(); + } + + private void InstallFromDirectory(string firmwareDirectory, string temporaryDirectory) + { + InstallFromPartition(new LocalFileSystem(firmwareDirectory), temporaryDirectory); + } + + private void InstallFromPartition(IFileSystem filesystem, string temporaryDirectory) + { + foreach (var entry in filesystem.EnumerateEntries("/", "*.nca")) + { + Nca nca = new Nca(_virtualFileSystem.KeySet, OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage()); + + SaveNca(nca, entry.Name.Remove(entry.Name.IndexOf('.')), temporaryDirectory); + } + } + + private void InstallFromCart(Xci gameCard, string temporaryDirectory) + { + if (gameCard.HasPartition(XciPartitionType.Update)) + { + XciPartition partition = gameCard.OpenPartition(XciPartitionType.Update); + + InstallFromPartition(partition, temporaryDirectory); + } + else + { + throw new Exception("Update not found in xci file."); + } + } + + private void InstallFromZip(ZipArchive archive, string temporaryDirectory) + { + using (archive) + { + foreach (var entry in archive.Entries) + { + if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00")) + { + // Clean up the name and get the NcaId + + string[] pathComponents = entry.FullName.Replace(".cnmt", "").Split('/'); + + string ncaId = pathComponents[pathComponents.Length - 1]; + + // If this is a fragmented nca, we need to get the previous element.GetZip + if (ncaId.Equals("00")) + { + ncaId = pathComponents[pathComponents.Length - 2]; + } + + if (ncaId.Contains(".nca")) + { + string newPath = Path.Combine(temporaryDirectory, ncaId); + + Directory.CreateDirectory(newPath); + + entry.ExtractToFile(Path.Combine(newPath, "00")); + } + } + } + } + } + + public void SaveNca(Nca nca, string ncaId, string temporaryDirectory) + { + string newPath = Path.Combine(temporaryDirectory, ncaId + ".nca"); + + Directory.CreateDirectory(newPath); + + using (FileStream file = File.Create(Path.Combine(newPath, "00"))) + { + nca.BaseStorage.AsStream().CopyTo(file); + } + } + + private IFile OpenPossibleFragmentedFile(IFileSystem filesystem, string path, OpenMode mode) + { + using var file = new UniqueRef(); + + if (filesystem.FileExists($"{path}/00")) + { + filesystem.OpenFile(ref file.Ref(), $"{path}/00".ToU8Span(), mode); + } + else + { + filesystem.OpenFile(ref file.Ref(), path.ToU8Span(), mode); + } + + return file.Release(); + } + + private Stream GetZipStream(ZipArchiveEntry entry) + { + MemoryStream dest = new MemoryStream(); + + Stream src = entry.Open(); + + src.CopyTo(dest); + src.Dispose(); + + return dest; + } + + public SystemVersion VerifyFirmwarePackage(string firmwarePackage) + { + _virtualFileSystem.ReloadKeySet(); + + // LibHac.NcaHeader's DecryptHeader doesn't check if HeaderKey is empty and throws InvalidDataException instead + // So, we check it early for a better user experience. + if (_virtualFileSystem.KeySet.HeaderKey.IsZeros()) + { + throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers."); + } + + Dictionary> updateNcas = new Dictionary>(); + + if (Directory.Exists(firmwarePackage)) + { + return VerifyAndGetVersionDirectory(firmwarePackage); + } + + if (!File.Exists(firmwarePackage)) + { + throw new FileNotFoundException("Firmware file does not exist."); + } + + FileInfo info = new FileInfo(firmwarePackage); + + using (FileStream file = File.OpenRead(firmwarePackage)) + { + switch (info.Extension) + { + case ".zip": + using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage)) + { + return VerifyAndGetVersionZip(archive); + } + case ".xci": + Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()); + + if (xci.HasPartition(XciPartitionType.Update)) + { + XciPartition partition = xci.OpenPartition(XciPartitionType.Update); + + return VerifyAndGetVersion(partition); + } + else + { + throw new InvalidFirmwarePackageException("Update not found in xci file."); + } + default: + break; + } + } + + SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory) + { + return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory)); + } + + SystemVersion VerifyAndGetVersionZip(ZipArchive archive) + { + SystemVersion systemVersion = null; + + foreach (var entry in archive.Entries) + { + if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00")) + { + using (Stream ncaStream = GetZipStream(entry)) + { + IStorage storage = ncaStream.AsStorage(); + + Nca nca = new Nca(_virtualFileSystem.KeySet, storage); + + if (updateNcas.ContainsKey(nca.Header.TitleId)) + { + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); + } + else + { + updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>()); + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); + } + } + } + } + + if (updateNcas.ContainsKey(SystemUpdateTitleId)) + { + var ncaEntry = updateNcas[SystemUpdateTitleId]; + + string metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path; + + CnmtContentMetaEntry[] metaEntries = null; + + var fileEntry = archive.GetEntry(metaPath); + + using (Stream ncaStream = GetZipStream(fileEntry)) + { + Nca metaNca = new Nca(_virtualFileSystem.KeySet, ncaStream.AsStorage()); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + using var metaFile = new UniqueRef(); + + if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.Get.AsStream()); + + if (meta.Type == ContentMetaType.SystemUpdate) + { + metaEntries = meta.MetaEntries; + + updateNcas.Remove(SystemUpdateTitleId); + }; + } + } + + if (metaEntries == null) + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + if (updateNcas.ContainsKey(SystemVersionTitleId)) + { + string versionEntry = updateNcas[SystemVersionTitleId].Find(x => x.type != NcaContentType.Meta).path; + + using (Stream ncaStream = GetZipStream(archive.GetEntry(versionEntry))) + { + Nca nca = new Nca(_virtualFileSystem.KeySet, ncaStream.AsStorage()); + + var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + using var systemVersionFile = new UniqueRef(); + + if (romfs.OpenFile(ref systemVersionFile.Ref(), "/file".ToU8Span(), OpenMode.Read).IsSuccess()) + { + systemVersion = new SystemVersion(systemVersionFile.Get.AsStream()); + } + } + } + + foreach (CnmtContentMetaEntry metaEntry in metaEntries) + { + if (updateNcas.TryGetValue(metaEntry.TitleId, out ncaEntry)) + { + metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path; + + string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path; + + // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it. + // This is a perfect valid case, so we should just ignore the missing content nca and continue. + if (contentPath == null) + { + updateNcas.Remove(metaEntry.TitleId); + + continue; + } + + ZipArchiveEntry metaZipEntry = archive.GetEntry(metaPath); + ZipArchiveEntry contentZipEntry = archive.GetEntry(contentPath); + + using (Stream metaNcaStream = GetZipStream(metaZipEntry)) + { + using (Stream contentNcaStream = GetZipStream(contentZipEntry)) + { + Nca metaNca = new Nca(_virtualFileSystem.KeySet, metaNcaStream.AsStorage()); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + using var metaFile = new UniqueRef(); + + if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.Get.AsStream()); + + IStorage contentStorage = contentNcaStream.AsStorage(); + if (contentStorage.GetSize(out long size).IsSuccess()) + { + byte[] contentData = new byte[size]; + + Span content = new Span(contentData); + + contentStorage.Read(0, content); + + Span hash = new Span(new byte[32]); + + LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash); + + if (LibHac.Common.Utilities.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash)) + { + updateNcas.Remove(metaEntry.TitleId); + } + } + } + } + } + } + } + + if (updateNcas.Count > 0) + { + string extraNcas = string.Empty; + + foreach (var entry in updateNcas) + { + foreach (var nca in entry.Value) + { + extraNcas += nca.path + Environment.NewLine; + } + } + + throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}"); + } + } + else + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + return systemVersion; + } + + SystemVersion VerifyAndGetVersion(IFileSystem filesystem) + { + SystemVersion systemVersion = null; + + CnmtContentMetaEntry[] metaEntries = null; + + foreach (var entry in filesystem.EnumerateEntries("/", "*.nca")) + { + IStorage ncaStorage = OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage(); + + Nca nca = new Nca(_virtualFileSystem.KeySet, ncaStorage); + + if (nca.Header.TitleId == SystemUpdateTitleId && nca.Header.ContentType == NcaContentType.Meta) + { + IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + using var metaFile = new UniqueRef(); + + if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.Get.AsStream()); + + if (meta.Type == ContentMetaType.SystemUpdate) + { + metaEntries = meta.MetaEntries; + } + }; + + continue; + } + else if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) + { + var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + using var systemVersionFile = new UniqueRef(); + + if (romfs.OpenFile(ref systemVersionFile.Ref(), "/file".ToU8Span(), OpenMode.Read).IsSuccess()) + { + systemVersion = new SystemVersion(systemVersionFile.Get.AsStream()); + } + } + + if (updateNcas.ContainsKey(nca.Header.TitleId)) + { + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath)); + } + else + { + updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>()); + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath)); + } + + ncaStorage.Dispose(); + } + + if (metaEntries == null) + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + foreach (CnmtContentMetaEntry metaEntry in metaEntries) + { + if (updateNcas.TryGetValue(metaEntry.TitleId, out var ncaEntry)) + { + var metaNcaEntry = ncaEntry.Find(x => x.type == NcaContentType.Meta); + string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path; + + // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it. + // This is a perfect valid case, so we should just ignore the missing content nca and continue. + if (contentPath == null) + { + updateNcas.Remove(metaEntry.TitleId); + + continue; + } + + IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaEntry.path, OpenMode.Read).AsStorage(); + IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentPath, OpenMode.Read).AsStorage(); + + Nca metaNca = new Nca(_virtualFileSystem.KeySet, metaStorage); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + using var metaFile = new UniqueRef(); + + if (fs.OpenFile(ref metaFile.Ref(), cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.Get.AsStream()); + + if (contentStorage.GetSize(out long size).IsSuccess()) + { + byte[] contentData = new byte[size]; + + Span content = new Span(contentData); + + contentStorage.Read(0, content); + + Span hash = new Span(new byte[32]); + + LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash); + + if (LibHac.Common.Utilities.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash)) + { + updateNcas.Remove(metaEntry.TitleId); + } + } + } + } + } + + if (updateNcas.Count > 0) + { + string extraNcas = string.Empty; + + foreach (var entry in updateNcas) + { + foreach (var (type, path) in entry.Value) + { + extraNcas += path + Environment.NewLine; + } + } + + throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}"); + } + + return systemVersion; + } + + return null; + } + + public SystemVersion GetCurrentFirmwareVersion() + { + LoadEntries(); + + lock (_lock) + { + var locationEnties = _locationEntries[StorageId.BuiltInSystem]; + + foreach (var entry in locationEnties) + { + if (entry.ContentType == NcaContentType.Data) + { + var path = _virtualFileSystem.SwitchPathToSystemPath(entry.ContentPath); + + using (FileStream fileStream = File.OpenRead(path)) + { + Nca nca = new Nca(_virtualFileSystem.KeySet, fileStream.AsStorage()); + + if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) + { + var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + using var systemVersionFile = new UniqueRef(); + + if (romfs.OpenFile(ref systemVersionFile.Ref(), "/file".ToU8Span(), OpenMode.Read).IsSuccess()) + { + return new SystemVersion(systemVersionFile.Get.AsStream()); + } + } + + } + } + } + } + + return null; + } + } +} diff --git a/Ryujinx.HLE/FileSystem/ContentPath.cs b/Ryujinx.HLE/FileSystem/ContentPath.cs new file mode 100644 index 00000000..c8663081 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/ContentPath.cs @@ -0,0 +1,82 @@ +using LibHac.Fs; +using LibHac.Ncm; +using Ryujinx.Common.Configuration; +using System; + +using static Ryujinx.HLE.FileSystem.VirtualFileSystem; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.FileSystem +{ + internal static class ContentPath + { + public const string SystemContent = "@SystemContent"; + public const string UserContent = "@UserContent"; + public const string SdCardContent = "@SdCardContent"; + public const string SdCard = "@Sdcard"; + public const string CalibFile = "@CalibFile"; + public const string Safe = "@Safe"; + public const string User = "@User"; + public const string System = "@System"; + public const string Host = "@Host"; + public const string GamecardApp = "@GcApp"; + public const string GamecardContents = "@GcS00000001"; + public const string GamecardUpdate = "@upp"; + public const string RegisteredUpdate = "@RegUpdate"; + + public const string Nintendo = "Nintendo"; + public const string Contents = "Contents"; + + public static string GetRealPath(VirtualFileSystem fileSystem, string switchContentPath) + { + return switchContentPath switch + { + SystemContent => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath, Contents), + UserContent => Path.Combine(AppDataManager.BaseDirPath, UserNandPath, Contents), + SdCardContent => Path.Combine(fileSystem.GetSdCardPath(), Nintendo, Contents), + System => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath), + User => Path.Combine(AppDataManager.BaseDirPath, UserNandPath), + _ => throw new NotSupportedException($"Content Path \"`{switchContentPath}`\" is not supported.") + }; + } + + public static string GetContentPath(ContentStorageId contentStorageId) + { + return contentStorageId switch + { + ContentStorageId.System => SystemContent, + ContentStorageId.User => UserContent, + ContentStorageId.SdCard => SdCardContent, + _ => throw new NotSupportedException($"Content Storage Id \"`{contentStorageId}`\" is not supported.") + }; + } + + public static string GetContentPath(StorageId storageId) + { + return storageId switch + { + StorageId.BuiltInSystem => SystemContent, + StorageId.BuiltInUser => UserContent, + StorageId.SdCard => SdCardContent, + _ => throw new NotSupportedException($"Storage Id \"`{storageId}`\" is not supported.") + }; + } + + public static StorageId GetStorageId(string contentPathString) + { + return contentPathString.Split(':')[0] switch + { + SystemContent or + System => StorageId.BuiltInSystem, + UserContent or + User => StorageId.BuiltInUser, + SdCardContent => StorageId.SdCard, + Host => StorageId.Host, + GamecardApp or + GamecardContents or + GamecardUpdate => StorageId.GameCard, + _ => StorageId.None + }; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/EncryptedFileSystemCreator.cs b/Ryujinx.HLE/FileSystem/EncryptedFileSystemCreator.cs index 60c3cfdb..f32dc2d7 100644 --- a/Ryujinx.HLE/FileSystem/EncryptedFileSystemCreator.cs +++ b/Ryujinx.HLE/FileSystem/EncryptedFileSystemCreator.cs @@ -8,7 +8,6 @@ namespace Ryujinx.HLE.FileSystem { public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator { - public Result Create(ref SharedRef outEncryptedFileSystem, ref SharedRef baseFileSystem, IEncryptedFileSystemCreator.KeyId idIndex, in EncryptionSeed encryptionSeed) @@ -18,10 +17,10 @@ namespace Ryujinx.HLE.FileSystem return ResultFs.InvalidArgument.Log(); } - // Todo: Reenable when AesXtsFileSystem is fixed + // TODO: Reenable when AesXtsFileSystem is fixed. outEncryptedFileSystem = SharedRef.CreateMove(ref baseFileSystem); return Result.Success; } } -} +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/LocationEntry.cs b/Ryujinx.HLE/FileSystem/LocationEntry.cs new file mode 100644 index 00000000..45cbc8cd --- /dev/null +++ b/Ryujinx.HLE/FileSystem/LocationEntry.cs @@ -0,0 +1,25 @@ +using LibHac.FsSystem; + +namespace Ryujinx.HLE.FileSystem +{ + public struct LocationEntry + { + public string ContentPath { get; private set; } + public int Flag { get; private set; } + public ulong TitleId { get; private set; } + public NcaContentType ContentType { get; private set; } + + public LocationEntry(string contentPath, int flag, ulong titleId, NcaContentType contentType) + { + ContentPath = contentPath; + Flag = flag; + TitleId = titleId; + ContentType = contentType; + } + + public void SetFlag(int flag) + { + Flag = flag; + } + } +} diff --git a/Ryujinx.HLE/FileSystem/SaveDataType.cs b/Ryujinx.HLE/FileSystem/SaveDataType.cs deleted file mode 100644 index 2207fc23..00000000 --- a/Ryujinx.HLE/FileSystem/SaveDataType.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.HLE.FileSystem -{ - enum SaveDataType : byte - { - SystemSaveData, - SaveData, - BcatDeliveryCacheStorage, - DeviceSaveData, - TemporaryStorage, - CacheStorage - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/SaveInfo.cs b/Ryujinx.HLE/FileSystem/SaveInfo.cs deleted file mode 100644 index 96f2f020..00000000 --- a/Ryujinx.HLE/FileSystem/SaveInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Ryujinx.HLE.HOS.Services.Account.Acc; - -namespace Ryujinx.HLE.FileSystem -{ - struct SaveInfo - { - public ulong TitleId { get; private set; } - public long SaveId { get; private set; } - public SaveDataType SaveDataType { get; private set; } - public SaveSpaceId SaveSpaceId { get; private set; } - public UserId UserId { get; private set; } - - public SaveInfo( - ulong titleId, - long saveId, - SaveDataType saveDataType, - SaveSpaceId saveSpaceId, - UserId userId = new UserId()) - { - TitleId = titleId; - SaveId = saveId; - SaveDataType = saveDataType; - SaveSpaceId = saveSpaceId; - UserId = userId; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/SaveSpaceId.cs b/Ryujinx.HLE/FileSystem/SaveSpaceId.cs deleted file mode 100644 index d51922df..00000000 --- a/Ryujinx.HLE/FileSystem/SaveSpaceId.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.FileSystem -{ - enum SaveSpaceId - { - NandSystem, - NandUser, - SdCard, - TemporaryStorage - } -} diff --git a/Ryujinx.HLE/FileSystem/StorageId.cs b/Ryujinx.HLE/FileSystem/StorageId.cs deleted file mode 100644 index d4043e2c..00000000 --- a/Ryujinx.HLE/FileSystem/StorageId.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.HLE.FileSystem -{ - public enum StorageId - { - None, - Host, - GameCard, - NandSystem, - NandUser, - SdCard - } -} diff --git a/Ryujinx.HLE/FileSystem/SystemVersion.cs b/Ryujinx.HLE/FileSystem/SystemVersion.cs new file mode 100644 index 00000000..a7926d5d --- /dev/null +++ b/Ryujinx.HLE/FileSystem/SystemVersion.cs @@ -0,0 +1,40 @@ +using Ryujinx.HLE.Utilities; +using System.IO; + +namespace Ryujinx.HLE.FileSystem +{ + public class SystemVersion + { + public byte Major { get; } + public byte Minor { get; } + public byte Micro { get; } + public byte RevisionMajor { get; } + public byte RevisionMinor { get; } + public string PlatformString { get; } + public string Hex { get; } + public string VersionString { get; } + public string VersionTitle { get; } + + public SystemVersion(Stream systemVersionFile) + { + using (BinaryReader reader = new BinaryReader(systemVersionFile)) + { + Major = reader.ReadByte(); + Minor = reader.ReadByte(); + Micro = reader.ReadByte(); + + reader.ReadByte(); // Padding + + RevisionMajor = reader.ReadByte(); + RevisionMinor = reader.ReadByte(); + + reader.ReadBytes(2); // Padding + + PlatformString = StringUtils.ReadInlinedAsciiString(reader, 0x20); + Hex = StringUtils.ReadInlinedAsciiString(reader, 0x40); + VersionString = StringUtils.ReadInlinedAsciiString(reader, 0x18); + VersionTitle = StringUtils.ReadInlinedAsciiString(reader, 0x80); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index 9359b03c..001a1f5f 100644 --- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -13,7 +13,6 @@ using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; -using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS; using System; using System.Buffers.Text; @@ -28,20 +27,29 @@ namespace Ryujinx.HLE.FileSystem { public class VirtualFileSystem : IDisposable { - public const string NandPath = AppDataManager.DefaultNandDir; - public const string SdCardPath = AppDataManager.DefaultSdcardDir; + public static string SafeNandPath = Path.Combine(AppDataManager.DefaultNandDir, "safe"); + public static string SystemNandPath = Path.Combine(AppDataManager.DefaultNandDir, "system"); + public static string UserNandPath = Path.Combine(AppDataManager.DefaultNandDir, "user"); - public static string SafeNandPath = Path.Combine(NandPath, "safe"); - public static string SystemNandPath = Path.Combine(NandPath, "system"); - public static string UserNandPath = Path.Combine(NandPath, "user"); + public KeySet KeySet { get; private set; } + public EmulatedGameCard GameCard { get; private set; } + public EmulatedSdCard SdCard { get; private set; } + public ModLoader ModLoader { get; private set; } + public Stream RomFs { get; private set; } private static bool _isInitialized = false; - public KeySet KeySet { get; private set; } - public EmulatedGameCard GameCard { get; private set; } - public EmulatedSdCard SdCard { get; private set; } + public static VirtualFileSystem CreateInstance() + { + if (_isInitialized) + { + throw new InvalidOperationException("VirtualFileSystem can only be instantiated once!"); + } - public ModLoader ModLoader { get; private set; } + _isInitialized = true; + + return new VirtualFileSystem(); + } private VirtualFileSystem() { @@ -49,8 +57,6 @@ namespace Ryujinx.HLE.FileSystem ModLoader = new ModLoader(); // Should only be created once } - public Stream RomFs { get; private set; } - public void LoadRomFs(string fileName) { RomFs = new FileStream(fileName, FileMode.Open, FileAccess.Read); @@ -79,7 +85,7 @@ namespace Ryujinx.HLE.FileSystem string fullPath = Path.GetFullPath(Path.Combine(basePath, fileName)); - if (!fullPath.StartsWith(GetBasePath())) + if (!fullPath.StartsWith(AppDataManager.BaseDirPath)) { return null; } @@ -87,14 +93,8 @@ namespace Ryujinx.HLE.FileSystem return fullPath; } - internal string GetBasePath() => AppDataManager.BaseDirPath; - internal string GetSdCardPath() => MakeFullPath(SdCardPath); - public string GetNandPath() => MakeFullPath(NandPath); - - public string GetFullPartitionPath(string partitionPath) - { - return MakeFullPath(partitionPath); - } + internal string GetSdCardPath() => MakeFullPath(AppDataManager.DefaultSdcardDir); + public string GetNandPath() => MakeFullPath(AppDataManager.DefaultNandDir); public string SwitchPathToSystemPath(string switchPath) { @@ -110,7 +110,7 @@ namespace Ryujinx.HLE.FileSystem public string SystemPathToSwitchPath(string systemPath) { - string baseSystemPath = GetBasePath() + Path.DirectorySeparatorChar; + string baseSystemPath = AppDataManager.BaseDirPath + Path.DirectorySeparatorChar; if (systemPath.StartsWith(baseSystemPath)) { @@ -136,8 +136,7 @@ namespace Ryujinx.HLE.FileSystem switch (path) { case ContentPath.SdCard: - case "@Sdcard": - path = SdCardPath; + path = AppDataManager.DefaultSdcardDir; break; case ContentPath.User: path = UserNandPath; @@ -146,7 +145,7 @@ namespace Ryujinx.HLE.FileSystem path = SystemNandPath; break; case ContentPath.SdCardContent: - path = Path.Combine(SdCardPath, "Nintendo", "Contents"); + path = Path.Combine(AppDataManager.DefaultSdcardDir, "Nintendo", "Contents"); break; case ContentPath.UserContent: path = Path.Combine(UserNandPath, "Contents"); @@ -156,27 +155,19 @@ namespace Ryujinx.HLE.FileSystem break; } - string fullPath = Path.Combine(GetBasePath(), path); + string fullPath = Path.Combine(AppDataManager.BaseDirPath, path); - if (isDirectory) + if (isDirectory && !Directory.Exists(fullPath)) { - if (!Directory.Exists(fullPath)) - { - Directory.CreateDirectory(fullPath); - } + Directory.CreateDirectory(fullPath); } return fullPath; } - public DriveInfo GetDrive() - { - return new DriveInfo(Path.GetPathRoot(GetBasePath())); - } - public void InitializeFsServer(LibHac.Horizon horizon, out HorizonClient fsServerClient) { - LocalFileSystem serverBaseFs = new LocalFileSystem(GetBasePath()); + LocalFileSystem serverBaseFs = new LocalFileSystem(AppDataManager.BaseDirPath); fsServerClient = horizon.CreatePrivilegedHorizonClient(); var fsServer = new FileSystemServer(fsServerClient); @@ -505,7 +496,7 @@ namespace Ryujinx.HLE.FileSystem bool canFixBySaveDataId = extraData.Attribute.StaticSaveDataId == 0 && info.StaticSaveDataId != 0; - bool hasEmptyOwnerId = extraData.OwnerId == 0 && info.Type != LibHac.Fs.SaveDataType.System; + bool hasEmptyOwnerId = extraData.OwnerId == 0 && info.Type != SaveDataType.System; if (!canFixByProgramId && !canFixBySaveDataId && !hasEmptyOwnerId) { @@ -523,7 +514,7 @@ namespace Ryujinx.HLE.FileSystem // The rest of the extra data can't be created from the save data info. // On user saves the owner ID will almost certainly be the same as the program ID. - if (info.Type != LibHac.Fs.SaveDataType.System) + if (info.Type != SaveDataType.System) { extraData.OwnerId = info.ProgramId.Value; } @@ -580,11 +571,6 @@ namespace Ryujinx.HLE.FileSystem } }; - public void Unload() - { - RomFs?.Dispose(); - } - public void Dispose() { Dispose(true); @@ -594,20 +580,8 @@ namespace Ryujinx.HLE.FileSystem { if (disposing) { - Unload(); + RomFs?.Dispose(); } } - - public static VirtualFileSystem CreateInstance() - { - if (_isInitialized) - { - throw new InvalidOperationException("VirtualFileSystem can only be instantiated once!"); - } - - _isInitialized = true; - - return new VirtualFileSystem(); - } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HLEConfiguration.cs b/Ryujinx.HLE/HLEConfiguration.cs index 3da02296..8fd02a96 100644 --- a/Ryujinx.HLE/HLEConfiguration.cs +++ b/Ryujinx.HLE/HLEConfiguration.cs @@ -3,7 +3,6 @@ using Ryujinx.Audio.Integration; using Ryujinx.Common.Configuration; using Ryujinx.Graphics.GAL; using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.SystemState; diff --git a/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs index c0d99a77..6780f802 100644 --- a/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs +++ b/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs @@ -2,10 +2,10 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Logging; -using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.SystemState; using System; @@ -106,7 +106,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error private string GetMessageText(uint module, uint description, string key) { - string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.NandSystem, NcaContentType.Data); + string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.BuiltInSystem, NcaContentType.Data); using (LibHac.Fs.IStorage ncaFileStream = new LocalStorage(_horizon.Device.FileSystem.SwitchPathToSystemPath(binaryTitleContentPath), FileAccess.Read, FileMode.Open)) { diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index cadd43ff..24d04c12 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -10,7 +10,7 @@ using Ryujinx.Audio.Integration; using Ryujinx.Audio.Output; using Ryujinx.Audio.Renderer.Device; using Ryujinx.Audio.Renderer.Server; -using Ryujinx.HLE.FileSystem.Content; +using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; @@ -238,6 +238,7 @@ namespace Ryujinx.HLE.HOS SurfaceFlinger = new SurfaceFlinger(device); InitializeAudioRenderer(); + InitializeServices(); } private void InitializeAudioRenderer() @@ -288,7 +289,7 @@ namespace Ryujinx.HLE.HOS AudioManager.Start(); } - public void InitializeServices() + private void InitializeServices() { SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext)); diff --git a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs index 5e396110..c985092b 100644 --- a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs +++ b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs @@ -1,4 +1,4 @@ -using Ryujinx.HLE.FileSystem; +using LibHac.Ncm; namespace Ryujinx.HLE.HOS.Services.Arp { @@ -20,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp { TitleId = 0x00, Version = 0x00, - BaseGameStorageId = (byte)StorageId.NandSystem, + BaseGameStorageId = (byte)StorageId.BuiltInSystem, UpdateGameStorageId = (byte)StorageId.None }; } @@ -35,7 +35,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp { TitleId = context.Device.Application.TitleId, Version = 0x00, - BaseGameStorageId = (byte)StorageId.NandSystem, + BaseGameStorageId = (byte)StorageId.BuiltInSystem, UpdateGameStorageId = (byte)StorageId.None }; } diff --git a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs index 94578303..01e1aa34 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -2,7 +2,6 @@ using LibHac; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Shim; -using LibHac.FsSrv; using LibHac.FsSrv.Impl; using LibHac.FsSystem; using LibHac.Ncm; @@ -19,7 +18,6 @@ using static Ryujinx.HLE.Utilities.StringUtils; using IFileSystem = LibHac.FsSrv.Sf.IFileSystem; using IStorage = LibHac.FsSrv.Sf.IStorage; using RightsId = LibHac.Fs.RightsId; -using StorageId = Ryujinx.HLE.FileSystem.StorageId; namespace Ryujinx.HLE.HOS.Services.Fs { diff --git a/Ryujinx.HLE/HOS/Services/Hid/Hid.cs b/Ryujinx.HLE/HOS/Services/Hid/Hid.cs index b8833e9e..b1466c78 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/Hid.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/Hid.cs @@ -55,9 +55,11 @@ namespace Ryujinx.HLE.HOS.Services.Hid _storage = storage; SharedMemory = SharedMemory.Create(); + + InitDevices(); } - public void InitDevices() + private void InitDevices() { DebugPad = new DebugPadDevice(_device, true); Touchscreen = new TouchDevice(_device, true); diff --git a/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs b/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs index 9f0c61dd..546c0567 100644 --- a/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs +++ b/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs @@ -1,4 +1,4 @@ -using Ryujinx.HLE.FileSystem; +using LibHac.Ncm; using Ryujinx.HLE.HOS.Services.Ncm.Lr.LocationResolverManager; namespace Ryujinx.HLE.HOS.Services.Ncm.Lr diff --git a/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs b/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs index 9928285d..0767b148 100644 --- a/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs +++ b/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs @@ -1,6 +1,6 @@ using LibHac.FsSystem; +using LibHac.Ncm; using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.FileSystem.Content; using System.Text; using static Ryujinx.HLE.Utilities.StringUtils; diff --git a/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs b/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs index 14d1990b..98f5f6a8 100644 --- a/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs @@ -1,5 +1,5 @@ -using Ryujinx.Common.Logging; -using Ryujinx.HLE.FileSystem; +using LibHac.Ncm; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Services.Arp; using Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface; diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs index 13ff1adc..f1ef6a2f 100644 --- a/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs @@ -2,11 +2,11 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Services.Sdb.Pl.Types; using System; @@ -63,7 +63,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl { if (contentManager.TryGetFontTitle(name, out ulong fontTitle) && contentManager.TryGetFontFilename(name, out string fontFilename)) { - string contentPath = contentManager.GetInstalledContentPath(fontTitle, StorageId.NandSystem, NcaContentType.Data); + string contentPath = contentManager.GetInstalledContentPath(fontTitle, StorageId.BuiltInSystem, NcaContentType.Data); string fontPath = _device.FileSystem.SwitchPathToSystemPath(contentPath); if (!string.IsNullOrWhiteSpace(fontPath)) diff --git a/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs b/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs index ef1a42ba..88888f34 100644 --- a/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs +++ b/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs @@ -3,9 +3,9 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Logging; -using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Utilities; using System; @@ -290,7 +290,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings { const ulong SystemVersionTitleId = 0x0100000000000809; - string contentPath = device.System.ContentManager.GetInstalledContentPath(SystemVersionTitleId, StorageId.NandSystem, NcaContentType.Data); + string contentPath = device.System.ContentManager.GetInstalledContentPath(SystemVersionTitleId, StorageId.BuiltInSystem, NcaContentType.Data); if (string.IsNullOrWhiteSpace(contentPath)) { diff --git a/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs b/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs index 15625fc2..53c9eb48 100644 --- a/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs @@ -1,5 +1,4 @@ -using Ryujinx.Common.Logging; -using Ryujinx.HLE.FileSystem.Content; +using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Services.Spl.Types; diff --git a/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs b/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs index b585224d..70691e1b 100644 --- a/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs +++ b/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs @@ -3,13 +3,13 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS.Services.Ssl.Types; using System; using System.Collections.Generic; @@ -82,7 +82,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl public string GetCertStoreTitleContentPath() { - return _contentManager.GetInstalledContentPath(CertStoreTitleId, StorageId.NandSystem, NcaContentType.Data); + return _contentManager.GetInstalledContentPath(CertStoreTitleId, StorageId.BuiltInSystem, NcaContentType.Data); } public bool HasCertStoreTitle() diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs index afcf976a..8ff09026 100644 --- a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs +++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs @@ -3,12 +3,12 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Logging; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS.Services.Time.Clock; using Ryujinx.HLE.Utilities; using System; @@ -241,7 +241,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone public string GetTimeZoneBinaryTitleContentPath() { - return _contentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.NandSystem, NcaContentType.Data); + return _contentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.BuiltInSystem, NcaContentType.Data); } public bool HasTimeZoneBinaryTitle() diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 0dcbc7ec..366a26f4 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -13,27 +13,17 @@ namespace Ryujinx.HLE { public class Switch : IDisposable { - public HLEConfiguration Configuration { get; } - + public HLEConfiguration Configuration { get; } public IHardwareDeviceDriver AudioDeviceDriver { get; } - - internal MemoryBlock Memory { get; } - - public GpuContext Gpu { get; } - - public VirtualFileSystem FileSystem => Configuration.VirtualFileSystem; - - public Horizon System { get; } - - public ApplicationLoader Application { get; } - - public PerformanceStatistics Statistics { get; } - - public Hid Hid { get; } - - public TamperMachine TamperMachine { get; } - - public IHostUiHandler UiHandler { get; } + public MemoryBlock Memory { get; } + public GpuContext Gpu { get; } + public VirtualFileSystem FileSystem { get; } + public Horizon System { get; } + public ApplicationLoader Application { get; } + public PerformanceStatistics Statistics { get; } + public Hid Hid { get; } + public TamperMachine TamperMachine { get; } + public IHostUiHandler UiHandler { get; } public bool EnableDeviceVsync { get; set; } = true; @@ -55,47 +45,27 @@ namespace Ryujinx.HLE } Configuration = configuration; + FileSystem = Configuration.VirtualFileSystem; + UiHandler = Configuration.HostUiHandler; + + AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver); + Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), MemoryAllocationFlags.Reserve); + Gpu = new GpuContext(Configuration.GpuRenderer); + System = new Horizon(this); + Statistics = new PerformanceStatistics(); + Hid = new Hid(this, System.HidStorage); + Application = new ApplicationLoader(this); + TamperMachine = new TamperMachine(); - UiHandler = configuration.HostUiHandler; - - AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(configuration.AudioDeviceDriver); - - Memory = new MemoryBlock(configuration.MemoryConfiguration.ToDramSize(), MemoryAllocationFlags.Reserve); - - Gpu = new GpuContext(configuration.GpuRenderer); - - System = new Horizon(this); - System.InitializeServices(); - - Statistics = new PerformanceStatistics(); - - Hid = new Hid(this, System.HidStorage); - Hid.InitDevices(); - - Application = new ApplicationLoader(this); - - TamperMachine = new TamperMachine(); - - Initialize(); - } - - private void Initialize() - { System.State.SetLanguage(Configuration.SystemLanguage); - System.State.SetRegion(Configuration.Region); - EnableDeviceVsync = Configuration.EnableVsync; - - System.State.DockedMode = Configuration.EnableDockedMode; - + EnableDeviceVsync = Configuration.EnableVsync; + System.State.DockedMode = Configuration.EnableDockedMode; System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default; - - System.EnablePtc = Configuration.EnablePtc; - - System.FsIntegrityCheckLevel = Configuration.FsIntegrityCheckLevel; - - System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode; + System.EnablePtc = Configuration.EnablePtc; + System.FsIntegrityCheckLevel = Configuration.FsIntegrityCheckLevel; + System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode; } public void LoadCart(string exeFsDir, string romFsFile = null) @@ -132,7 +102,6 @@ namespace Ryujinx.HLE { Gpu.ProcessShaderCacheQueue(); Gpu.Renderer.PreFrame(); - Gpu.GPFifo.DispatchCalls(); } @@ -182,9 +151,9 @@ namespace Ryujinx.HLE { System.Dispose(); AudioDeviceDriver.Dispose(); - FileSystem.Unload(); + FileSystem.Dispose(); Memory.Dispose(); } } } -} +} \ No newline at end of file diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs index 5915a881..1d64a8c6 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/Ryujinx.Headless.SDL2/Program.cs @@ -20,7 +20,6 @@ using Ryujinx.Graphics.OpenGL; using Ryujinx.Headless.SDL2.OpenGL; using Ryujinx.HLE; using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.Input; @@ -29,7 +28,6 @@ using Ryujinx.Input.SDL2; using System; using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Text.Json; using System.Threading; diff --git a/Ryujinx/Ui/Helper/SetupValidator.cs b/Ryujinx/Ui/Helper/SetupValidator.cs index 45315f8f..8be8497b 100644 --- a/Ryujinx/Ui/Helper/SetupValidator.cs +++ b/Ryujinx/Ui/Helper/SetupValidator.cs @@ -1,5 +1,5 @@ using Ryujinx.Common.Logging; -using Ryujinx.HLE.FileSystem.Content; +using Ryujinx.HLE.FileSystem; using Ryujinx.Ui.Widgets; using System; using System.IO; diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 32e66ee4..54e28765 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -1,18 +1,10 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; - -using ARMeilleure.Translation; +using ARMeilleure.Translation; using ARMeilleure.Translation.PTC; - using Gtk; - using LibHac.Common; using LibHac.Common.Keys; using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Ns; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.Dummy; @@ -29,7 +21,6 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.SystemState; @@ -42,9 +33,14 @@ using Ryujinx.Ui.Applet; using Ryujinx.Ui.Helper; using Ryujinx.Ui.Widgets; using Ryujinx.Ui.Windows; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; using GUI = Gtk.Builder.ObjectAttribute; - using PtcLoadingState = ARMeilleure.Translation.PTC.PtcLoadingState; using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; @@ -1283,7 +1279,7 @@ namespace Ryujinx.Ui private void Load_Mii_Edit_Applet(object sender, EventArgs args) { - string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.NandSystem, NcaContentType.Program); + string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); LoadApplication(contentPath); } diff --git a/Ryujinx/Ui/Windows/AvatarWindow.cs b/Ryujinx/Ui/Windows/AvatarWindow.cs index 687b3d17..a5c07aed 100644 --- a/Ryujinx/Ui/Windows/AvatarWindow.cs +++ b/Ryujinx/Ui/Windows/AvatarWindow.cs @@ -3,10 +3,10 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.FileSystem.Content; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; @@ -115,7 +115,7 @@ namespace Ryujinx.Ui.Windows return; } - string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.NandSystem, NcaContentType.Data); + string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data); string avatarPath = virtualFileSystem.SwitchPathToSystemPath(contentPath); if (!string.IsNullOrWhiteSpace(avatarPath)) diff --git a/Ryujinx/Ui/Windows/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs index 2b423081..f0e93730 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.cs +++ b/Ryujinx/Ui/Windows/SettingsWindow.cs @@ -105,9 +105,9 @@ namespace Ryujinx.Ui.Windows #pragma warning restore CS0649, IDE0044 - public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : this(parent, new Builder("Ryujinx.Ui.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } + public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Ui.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } - private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, HLE.FileSystem.Content.ContentManager contentManager) : base(builder.GetObject("_settingsWin").Handle) + private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetObject("_settingsWin").Handle) { Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Ryujinx.png"); diff --git a/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs b/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs index b26e7e20..862d5f15 100644 --- a/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs +++ b/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs @@ -1,6 +1,5 @@ using Gtk; using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.Ui.Widgets; using SixLabors.ImageSharp; @@ -12,7 +11,7 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Image = SixLabors.ImageSharp.Image; +using Image = SixLabors.ImageSharp.Image; using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId; namespace Ryujinx.Ui.Windows -- cgit v1.2.3