diff options
Diffstat (limited to 'Ryujinx.HLE/FileSystem/Content')
-rw-r--r-- | Ryujinx.HLE/FileSystem/Content/ContentManager.cs | 300 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/Content/ContentPath.cs | 19 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/Content/LocationEntry.cs | 28 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/Content/LocationHelper.cs | 91 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/Content/StorageId.cs | 9 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/Content/TitleType.cs | 15 |
6 files changed, 462 insertions, 0 deletions
diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs new file mode 100644 index 00000000..025eb261 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs @@ -0,0 +1,300 @@ +using LibHac; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.HLE.FileSystem.Content +{ + internal class ContentManager + { + private Dictionary<StorageId, LinkedList<LocationEntry>> LocationEntries; + + private Dictionary<string, long> SharedFontTitleDictionary; + + private SortedDictionary<(ulong, ContentType), string> ContentDictionary; + + private Switch Device; + + public ContentManager(Switch Device) + { + ContentDictionary = new SortedDictionary<(ulong, ContentType), string>(); + LocationEntries = new Dictionary<StorageId, LinkedList<LocationEntry>>(); + + SharedFontTitleDictionary = new Dictionary<string, long>() + { + { "FontStandard", 0x0100000000000811 }, + { "FontChineseSimplified", 0x0100000000000814 }, + { "FontExtendedChineseSimplified", 0x0100000000000814 }, + { "FontKorean", 0x0100000000000812 }, + { "FontChineseTraditional", 0x0100000000000813 }, + { "FontNintendoExtended" , 0x0100000000000810 }, + }; + + this.Device = Device; + } + + public void LoadEntries() + { + ContentDictionary = new SortedDictionary<(ulong, ContentType), string>(); + + foreach (StorageId StorageId in Enum.GetValues(typeof(StorageId))) + { + string ContentDirectory = null; + string ContentPathString = null; + string RegisteredDirectory = null; + + try + { + ContentPathString = LocationHelper.GetContentRoot(StorageId); + ContentDirectory = LocationHelper.GetRealPath(Device.FileSystem, ContentPathString); + RegisteredDirectory = Path.Combine(ContentDirectory, "registered"); + } + catch (NotSupportedException NEx) + { + continue; + } + + Directory.CreateDirectory(RegisteredDirectory); + + LinkedList<LocationEntry> LocationList = new LinkedList<LocationEntry>(); + + void AddEntry(LocationEntry Entry) + { + LocationList.AddLast(Entry); + } + + foreach (string DirectoryPath in Directory.EnumerateDirectories(RegisteredDirectory)) + { + if (Directory.GetFiles(DirectoryPath).Length > 0) + { + string NcaName = new DirectoryInfo(DirectoryPath).Name.Replace(".nca", string.Empty); + + using (FileStream NcaFile = new FileStream(Directory.GetFiles(DirectoryPath)[0], FileMode.Open, FileAccess.Read)) + { + Nca Nca = new Nca(Device.System.KeySet, NcaFile, false); + + string SwitchPath = Path.Combine(ContentPathString + ":", + NcaFile.Name.Replace(ContentDirectory, string.Empty).TrimStart('\\')); + + // Change path format to switch's + SwitchPath = SwitchPath.Replace('\\', '/'); + + LocationEntry Entry = new LocationEntry(SwitchPath, + 0, + (long)Nca.Header.TitleId, + Nca.Header.ContentType); + + AddEntry(Entry); + + ContentDictionary.Add((Nca.Header.TitleId, Nca.Header.ContentType), NcaName); + + NcaFile.Close(); + Nca.Dispose(); + NcaFile.Dispose(); + } + } + } + + foreach (string FilePath in Directory.EnumerateFiles(ContentDirectory)) + { + if (Path.GetExtension(FilePath) == ".nca") + { + string NcaName = Path.GetFileNameWithoutExtension(FilePath); + + using (FileStream NcaFile = new FileStream(FilePath, FileMode.Open, FileAccess.Read)) + { + Nca Nca = new Nca(Device.System.KeySet, NcaFile, false); + + string SwitchPath = Path.Combine(ContentPathString + ":", + FilePath.Replace(ContentDirectory, string.Empty).TrimStart('\\')); + + // Change path format to switch's + SwitchPath = SwitchPath.Replace('\\', '/'); + + LocationEntry Entry = new LocationEntry(SwitchPath, + 0, + (long)Nca.Header.TitleId, + Nca.Header.ContentType); + + AddEntry(Entry); + + ContentDictionary.Add((Nca.Header.TitleId, Nca.Header.ContentType), NcaName); + + NcaFile.Close(); + Nca.Dispose(); + NcaFile.Dispose(); + } + } + } + + if(LocationEntries.ContainsKey(StorageId) && LocationEntries[StorageId]?.Count == 0) + { + LocationEntries.Remove(StorageId); + } + + if (!LocationEntries.ContainsKey(StorageId)) + { + LocationEntries.Add(StorageId, LocationList); + } + } + } + + public void ClearEntry(long TitleId, ContentType ContentType,StorageId StorageId) + { + RemoveLocationEntry(TitleId, ContentType, StorageId); + } + + public void RefreshEntries(StorageId StorageId, int Flag) + { + LinkedList<LocationEntry> LocationList = LocationEntries[StorageId]; + LinkedListNode<LocationEntry> LocationEntry = LocationList.First; + + while (LocationEntry != null) + { + LinkedListNode<LocationEntry> NextLocationEntry = LocationEntry.Next; + + if (LocationEntry.Value.Flag == Flag) + { + LocationList.Remove(LocationEntry.Value); + } + + LocationEntry = NextLocationEntry; + } + } + + public bool HasNca(string NcaId, StorageId StorageId) + { + if (ContentDictionary.ContainsValue(NcaId)) + { + var Content = ContentDictionary.FirstOrDefault(x => x.Value == NcaId); + long TitleId = (long)Content.Key.Item1; + ContentType ContentType = Content.Key.Item2; + StorageId Storage = GetInstalledStorage(TitleId, ContentType, StorageId); + + return Storage == StorageId; + } + + return false; + } + + public UInt128 GetInstalledNcaId(long TitleId, ContentType ContentType) + { + if (ContentDictionary.ContainsKey(((ulong)TitleId,ContentType))) + { + return new UInt128(ContentDictionary[((ulong)TitleId,ContentType)]); + } + + return new UInt128(); + } + + public StorageId GetInstalledStorage(long TitleId, ContentType ContentType, StorageId StorageId) + { + LocationEntry LocationEntry = GetLocation(TitleId, ContentType, StorageId); + + return LocationEntry.ContentPath != null ? + LocationHelper.GetStorageId(LocationEntry.ContentPath) : StorageId.None; + } + + public string GetInstalledContentPath(long TitleId, StorageId StorageId, ContentType ContentType) + { + LocationEntry LocationEntry = GetLocation(TitleId, ContentType, StorageId); + + if (VerifyContentType(LocationEntry, ContentType)) + { + return LocationEntry.ContentPath; + } + + return string.Empty; + } + + public void RedirectLocation(LocationEntry NewEntry, StorageId StorageId) + { + LocationEntry LocationEntry = GetLocation(NewEntry.TitleId, NewEntry.ContentType, StorageId); + + if (LocationEntry.ContentPath != null) + { + RemoveLocationEntry(NewEntry.TitleId, NewEntry.ContentType, StorageId); + } + + AddLocationEntry(NewEntry, StorageId); + } + + private bool VerifyContentType(LocationEntry LocationEntry, ContentType ContentType) + { + StorageId StorageId = LocationHelper.GetStorageId(LocationEntry.ContentPath); + string InstalledPath = Device.FileSystem.SwitchPathToSystemPath(LocationEntry.ContentPath); + + if (!string.IsNullOrWhiteSpace(InstalledPath)) + { + if (File.Exists(InstalledPath)) + { + FileStream File = new FileStream(InstalledPath, FileMode.Open, FileAccess.Read); + Nca Nca = new Nca(Device.System.KeySet, File, false); + bool ContentCheck = Nca.Header.ContentType == ContentType; + + Nca.Dispose(); + File.Dispose(); + + return ContentCheck; + } + } + + return false; + } + + private void AddLocationEntry(LocationEntry Entry, StorageId StorageId) + { + LinkedList<LocationEntry> LocationList = null; + + if (LocationEntries.ContainsKey(StorageId)) + { + LocationList = LocationEntries[StorageId]; + } + + if (LocationList != null) + { + if (LocationList.Contains(Entry)) + { + LocationList.Remove(Entry); + } + + LocationList.AddLast(Entry); + } + } + + private void RemoveLocationEntry(long TitleId, ContentType ContentType, StorageId StorageId) + { + LinkedList<LocationEntry> LocationList = null; + + if (LocationEntries.ContainsKey(StorageId)) + { + LocationList = LocationEntries[StorageId]; + } + + if (LocationList != null) + { + LocationEntry Entry = + LocationList.ToList().Find(x => x.TitleId == TitleId && x.ContentType == ContentType); + + if (Entry.ContentPath != null) + { + LocationList.Remove(Entry); + } + } + } + + public bool TryGetFontTitle(string FontName, out long TitleId) + { + return SharedFontTitleDictionary.TryGetValue(FontName, out TitleId); + } + + private LocationEntry GetLocation(long TitleId, ContentType ContentType,StorageId StorageId) + { + LinkedList<LocationEntry> LocationList = LocationEntries[StorageId]; + + return LocationList.ToList().Find(x => x.TitleId == TitleId && x.ContentType == ContentType); + } + } +} diff --git a/Ryujinx.HLE/FileSystem/Content/ContentPath.cs b/Ryujinx.HLE/FileSystem/Content/ContentPath.cs new file mode 100644 index 00000000..1e2c8ab3 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/ContentPath.cs @@ -0,0 +1,19 @@ +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 new file mode 100644 index 00000000..c7c6133b --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/LocationEntry.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; +using LibHac; + +namespace Ryujinx.HLE.FileSystem.Content +{ + public struct LocationEntry + { + public string ContentPath { get; private set; } + public int Flag { get; private set; } + public long TitleId { get; private set; } + public ContentType ContentType { get; private set; } + + public LocationEntry(string ContentPath, int Flag, long TitleId, ContentType ContentType) + { + this.ContentPath = ContentPath; + this.Flag = Flag; + this.TitleId = TitleId; + this.ContentType = ContentType; + } + + public void SetFlag(int Flag) + { + this.Flag = Flag; + } + } +} diff --git a/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs b/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs new file mode 100644 index 00000000..75b59431 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/LocationHelper.cs @@ -0,0 +1,91 @@ +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(FileSystem.GetBasePath(), SystemNandPath, "Contents"); + case ContentPath.UserContent: + return Path.Combine(FileSystem.GetBasePath(), 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 new file mode 100644 index 00000000..4ff3dd65 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/StorageId.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.FileSystem.Content +{ + public enum ContentStorageId + { + NandSystem, + NandUser, + SdCard + } +} diff --git a/Ryujinx.HLE/FileSystem/Content/TitleType.cs b/Ryujinx.HLE/FileSystem/Content/TitleType.cs new file mode 100644 index 00000000..6ad26c9c --- /dev/null +++ b/Ryujinx.HLE/FileSystem/Content/TitleType.cs @@ -0,0 +1,15 @@ +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 + } +} |