diff options
Diffstat (limited to 'Ryujinx.HLE/FileSystem')
-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 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/FileSystemProvider.cs | 281 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/IFileSystemProvider.cs | 41 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/PFsProvider.cs | 146 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/RomFsProvider.cs | 163 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/SaveSpaceId.cs | 2 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/StorageId.cs | 12 | ||||
-rw-r--r-- | Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 39 |
13 files changed, 1143 insertions, 3 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 + } +} diff --git a/Ryujinx.HLE/FileSystem/FileSystemProvider.cs b/Ryujinx.HLE/FileSystem/FileSystemProvider.cs new file mode 100644 index 00000000..37ccfb10 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/FileSystemProvider.cs @@ -0,0 +1,281 @@ +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.FspSrv; +using System; +using System.Collections.Generic; +using System.IO; + +using static Ryujinx.HLE.HOS.ErrorCode; + +namespace Ryujinx.HLE.FileSystem +{ + class FileSystemProvider : IFileSystemProvider + { + private readonly string BasePath; + private readonly string RootPath; + + public FileSystemProvider(string BasePath, string RootPath) + { + this.BasePath = BasePath; + this.RootPath = RootPath; + + CheckIfDecendentOfRootPath(BasePath); + } + + public long CreateDirectory(string Name) + { + CheckIfDecendentOfRootPath(Name); + + if (Directory.Exists(Name)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists); + } + + Directory.CreateDirectory(Name); + + return 0; + } + + public long CreateFile(string Name, long Size) + { + CheckIfDecendentOfRootPath(Name); + + if (File.Exists(Name)) + { + return MakeError(ErrorModule.Fs, FsErr.PathAlreadyExists); + } + + using (FileStream NewFile = File.Create(Name)) + { + NewFile.SetLength(Size); + } + + return 0; + } + + public long DeleteDirectory(string Name, bool Recursive) + { + CheckIfDecendentOfRootPath(Name); + + string DirName = Name; + + if (!Directory.Exists(DirName)) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + Directory.Delete(DirName, Recursive); + + return 0; + } + + public long DeleteFile(string Name) + { + CheckIfDecendentOfRootPath(Name); + + if (!File.Exists(Name)) + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + else + { + File.Delete(Name); + } + + return 0; + } + + public DirectoryEntry[] GetDirectories(string Path) + { + CheckIfDecendentOfRootPath(Path); + + List<DirectoryEntry> Entries = new List<DirectoryEntry>(); + + foreach(string Directory in Directory.EnumerateDirectories(Path)) + { + DirectoryEntry DirectoryEntry = new DirectoryEntry(Directory, DirectoryEntryType.Directory); + + Entries.Add(DirectoryEntry); + } + + return Entries.ToArray(); + } + + public DirectoryEntry[] GetEntries(string Path) + { + CheckIfDecendentOfRootPath(Path); + + if (Directory.Exists(Path)) + { + List<DirectoryEntry> Entries = new List<DirectoryEntry>(); + + foreach (string Directory in Directory.EnumerateDirectories(Path)) + { + DirectoryEntry DirectoryEntry = new DirectoryEntry(Directory, DirectoryEntryType.Directory); + + Entries.Add(DirectoryEntry); + } + + foreach (string File in Directory.EnumerateFiles(Path)) + { + FileInfo FileInfo = new FileInfo(File); + DirectoryEntry DirectoryEntry = new DirectoryEntry(File, DirectoryEntryType.File, FileInfo.Length); + + Entries.Add(DirectoryEntry); + } + } + + return null; + } + + public DirectoryEntry[] GetFiles(string Path) + { + CheckIfDecendentOfRootPath(Path); + + List<DirectoryEntry> Entries = new List<DirectoryEntry>(); + + foreach (string File in Directory.EnumerateFiles(Path)) + { + FileInfo FileInfo = new FileInfo(File); + DirectoryEntry DirectoryEntry = new DirectoryEntry(File, DirectoryEntryType.File, FileInfo.Length); + + Entries.Add(DirectoryEntry); + } + + return Entries.ToArray(); + } + + public long GetFreeSpace(ServiceCtx Context) + { + return Context.Device.FileSystem.GetDrive().AvailableFreeSpace; + } + + public string GetFullPath(string Name) + { + if (Name.StartsWith("//")) + { + Name = Name.Substring(2); + } + else if (Name.StartsWith('/')) + { + Name = Name.Substring(1); + } + else + { + return null; + } + + string FullPath = Path.Combine(BasePath, Name); + + CheckIfDecendentOfRootPath(FullPath); + + return FullPath; + } + + public long GetTotalSpace(ServiceCtx Context) + { + return Context.Device.FileSystem.GetDrive().TotalSize; + } + + public bool DirectoryExists(string Name) + { + CheckIfDecendentOfRootPath(Name); + + return Directory.Exists(Name); + } + + public bool FileExists(string Name) + { + CheckIfDecendentOfRootPath(Name); + + return File.Exists(Name); + } + + public long OpenDirectory(string Name, int FilterFlags, out IDirectory DirectoryInterface) + { + CheckIfDecendentOfRootPath(Name); + + if (Directory.Exists(Name)) + { + DirectoryInterface = new IDirectory(Name, FilterFlags, this); + + return 0; + } + + DirectoryInterface = null; + + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + public long OpenFile(string Name, out IFile FileInterface) + { + CheckIfDecendentOfRootPath(Name); + + if (File.Exists(Name)) + { + FileStream Stream = new FileStream(Name, FileMode.Open); + + FileInterface = new IFile(Stream, Name); + + return 0; + } + + FileInterface = null; + + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + public long RenameDirectory(string OldName, string NewName) + { + CheckIfDecendentOfRootPath(OldName); + CheckIfDecendentOfRootPath(NewName); + + if (Directory.Exists(OldName)) + { + Directory.Move(OldName, NewName); + } + else + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + return 0; + } + + public long RenameFile(string OldName, string NewName) + { + CheckIfDecendentOfRootPath(OldName); + CheckIfDecendentOfRootPath(NewName); + + if (File.Exists(OldName)) + { + File.Move(OldName, NewName); + } + else + { + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + return 0; + } + + public void CheckIfDecendentOfRootPath(string Path) + { + DirectoryInfo PathInfo = new DirectoryInfo(Path); + DirectoryInfo RootInfo = new DirectoryInfo(RootPath); + + while (PathInfo.Parent != null) + { + if (PathInfo.Parent.FullName == RootInfo.FullName) + { + return; + } + else + { + PathInfo = PathInfo.Parent; + } + } + + throw new InvalidOperationException($"Path {Path} is not a child directory of {RootPath}"); + } + } +} diff --git a/Ryujinx.HLE/FileSystem/IFileSystemProvider.cs b/Ryujinx.HLE/FileSystem/IFileSystemProvider.cs new file mode 100644 index 00000000..88a630a0 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/IFileSystemProvider.cs @@ -0,0 +1,41 @@ +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.FspSrv; +using System; + +namespace Ryujinx.HLE.FileSystem +{ + interface IFileSystemProvider + { + long CreateFile(string Name, long Size); + + long CreateDirectory(string Name); + + long RenameFile(string OldName, string NewName); + + long RenameDirectory(string OldName, string NewName); + + DirectoryEntry[] GetEntries(string Path); + + DirectoryEntry[] GetDirectories(string Path); + + DirectoryEntry[] GetFiles(string Path); + + long DeleteFile(string Name); + + long DeleteDirectory(string Name, bool Recursive); + + bool FileExists(string Name); + + bool DirectoryExists(string Name); + + long OpenFile(string Name, out IFile FileInterface); + + long OpenDirectory(string Name, int FilterFlags, out IDirectory DirectoryInterface); + + string GetFullPath(string Name); + + long GetFreeSpace(ServiceCtx Context); + + long GetTotalSpace(ServiceCtx Context); + } +} diff --git a/Ryujinx.HLE/FileSystem/PFsProvider.cs b/Ryujinx.HLE/FileSystem/PFsProvider.cs new file mode 100644 index 00000000..c901f073 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/PFsProvider.cs @@ -0,0 +1,146 @@ +using LibHac; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.FspSrv; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using static Ryujinx.HLE.HOS.ErrorCode; + +namespace Ryujinx.HLE.FileSystem +{ + class PFsProvider : IFileSystemProvider + { + private Pfs Pfs; + + public PFsProvider(Pfs Pfs) + { + this.Pfs = Pfs; + } + + public long CreateDirectory(string Name) + { + throw new NotSupportedException(); + } + + public long CreateFile(string Name, long Size) + { + throw new NotSupportedException(); + } + + public long DeleteDirectory(string Name, bool Recursive) + { + throw new NotSupportedException(); + } + + public long DeleteFile(string Name) + { + throw new NotSupportedException(); + } + + public DirectoryEntry[] GetDirectories(string Path) + { + return new DirectoryEntry[0]; + } + + public DirectoryEntry[] GetEntries(string Path) + { + List<DirectoryEntry> Entries = new List<DirectoryEntry>(); + + foreach (PfsFileEntry File in Pfs.Files) + { + DirectoryEntry DirectoryEntry = new DirectoryEntry(File.Name, DirectoryEntryType.File, File.Size); + + Entries.Add(DirectoryEntry); + } + + return Entries.ToArray(); + } + + public DirectoryEntry[] GetFiles(string Path) + { + List<DirectoryEntry> Entries = new List<DirectoryEntry>(); + + foreach (PfsFileEntry File in Pfs.Files) + { + DirectoryEntry DirectoryEntry = new DirectoryEntry(File.Name, DirectoryEntryType.File, File.Size); + + Entries.Add(DirectoryEntry); + } + + return Entries.ToArray(); + } + + public long GetFreeSpace(ServiceCtx Context) + { + return 0; + } + + public string GetFullPath(string Name) + { + return Name; + } + + public long GetTotalSpace(ServiceCtx Context) + { + return Pfs.Files.Sum(x => x.Size); + } + + public bool DirectoryExists(string Name) + { + return Name == "/" ? true : false; + } + + public bool FileExists(string Name) + { + Name = Name.TrimStart('/'); + + return Pfs.FileExists(Name); + } + + public long OpenDirectory(string Name, int FilterFlags, out IDirectory DirectoryInterface) + { + if (Name == "/") + { + DirectoryInterface = new IDirectory(Name, FilterFlags, this); + + return 0; + } + + throw new NotSupportedException(); + } + + public long OpenFile(string Name, out IFile FileInterface) + { + Name = Name.TrimStart('/'); + + if (Pfs.FileExists(Name)) + { + Stream Stream = Pfs.OpenFile(Name); + FileInterface = new IFile(Stream, Name); + + return 0; + } + + FileInterface = null; + + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + public long RenameDirectory(string OldName, string NewName) + { + throw new NotSupportedException(); + } + + public long RenameFile(string OldName, string NewName) + { + throw new NotSupportedException(); + } + + public void CheckIfOutsideBasePath(string Path) + { + throw new NotSupportedException(); + } + } +} diff --git a/Ryujinx.HLE/FileSystem/RomFsProvider.cs b/Ryujinx.HLE/FileSystem/RomFsProvider.cs new file mode 100644 index 00000000..d966d3d7 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/RomFsProvider.cs @@ -0,0 +1,163 @@ +using LibHac; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.FspSrv; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using static Ryujinx.HLE.HOS.ErrorCode; + +namespace Ryujinx.HLE.FileSystem +{ + class RomFsProvider : IFileSystemProvider + { + private Romfs RomFs; + + public RomFsProvider(Stream StorageStream) + { + RomFs = new Romfs(StorageStream); + } + + public long CreateDirectory(string Name) + { + throw new NotSupportedException(); + } + + public long CreateFile(string Name, long Size) + { + throw new NotSupportedException(); + } + + public long DeleteDirectory(string Name, bool Recursive) + { + throw new NotSupportedException(); + } + + public long DeleteFile(string Name) + { + throw new NotSupportedException(); + } + + public DirectoryEntry[] GetDirectories(string Path) + { + List<DirectoryEntry> Directories = new List<DirectoryEntry>(); + + foreach(RomfsDir Directory in RomFs.Directories) + { + DirectoryEntry DirectoryEntry = new DirectoryEntry(Directory.Name, DirectoryEntryType.Directory); + + Directories.Add(DirectoryEntry); + } + + return Directories.ToArray(); + } + + public DirectoryEntry[] GetEntries(string Path) + { + List<DirectoryEntry> Entries = new List<DirectoryEntry>(); + + foreach (RomfsDir Directory in RomFs.Directories) + { + DirectoryEntry DirectoryEntry = new DirectoryEntry(Directory.Name, DirectoryEntryType.Directory); + + Entries.Add(DirectoryEntry); + } + + foreach (RomfsFile File in RomFs.Files) + { + DirectoryEntry DirectoryEntry = new DirectoryEntry(File.Name, DirectoryEntryType.File, File.DataLength); + + Entries.Add(DirectoryEntry); + } + + return Entries.ToArray(); + } + + public DirectoryEntry[] GetFiles(string Path) + { + List<DirectoryEntry> Files = new List<DirectoryEntry>(); + + foreach (RomfsFile File in RomFs.Files) + { + DirectoryEntry DirectoryEntry = new DirectoryEntry(File.Name, DirectoryEntryType.File, File.DataLength); + + Files.Add(DirectoryEntry); + } + + return Files.ToArray(); + } + + public long GetFreeSpace(ServiceCtx Context) + { + return 0; + } + + public string GetFullPath(string Name) + { + return Name; + } + + public long GetTotalSpace(ServiceCtx Context) + { + return RomFs.Files.Sum(x => x.DataLength); + } + + public bool DirectoryExists(string Name) + { + return RomFs.Directories.Exists(x=>x.Name == Name); + } + + public bool FileExists(string Name) + { + return RomFs.FileExists(Name); + } + + public long OpenDirectory(string Name, int FilterFlags, out IDirectory DirectoryInterface) + { + RomfsDir Directory = RomFs.Directories.Find(x => x.Name == Name); + + if (Directory != null) + { + DirectoryInterface = new IDirectory(Name, FilterFlags, this); + + return 0; + } + + DirectoryInterface = null; + + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + public long OpenFile(string Name, out IFile FileInterface) + { + if (RomFs.FileExists(Name)) + { + Stream Stream = RomFs.OpenFile(Name); + + FileInterface = new IFile(Stream, Name); + + return 0; + } + + FileInterface = null; + + return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist); + } + + public long RenameDirectory(string OldName, string NewName) + { + throw new NotSupportedException(); + } + + public long RenameFile(string OldName, string NewName) + { + throw new NotSupportedException(); + } + + public void CheckIfOutsideBasePath(string Path) + { + throw new NotSupportedException(); + } + } +} diff --git a/Ryujinx.HLE/FileSystem/SaveSpaceId.cs b/Ryujinx.HLE/FileSystem/SaveSpaceId.cs index 5a2b32d6..d51922df 100644 --- a/Ryujinx.HLE/FileSystem/SaveSpaceId.cs +++ b/Ryujinx.HLE/FileSystem/SaveSpaceId.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.FileSystem { - enum SaveSpaceId : byte + enum SaveSpaceId { NandSystem, NandUser, diff --git a/Ryujinx.HLE/FileSystem/StorageId.cs b/Ryujinx.HLE/FileSystem/StorageId.cs new file mode 100644 index 00000000..1ef38e01 --- /dev/null +++ b/Ryujinx.HLE/FileSystem/StorageId.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.FileSystem +{ + internal enum StorageId + { + None, + Host, + GameCard, + NandSystem, + NandUser, + SdCard + } +} diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index e621ec2b..6bb2847f 100644 --- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -1,3 +1,4 @@ +using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS; using System; using System.IO; @@ -11,6 +12,7 @@ namespace Ryujinx.HLE.FileSystem public const string SdCardPath = "sdmc"; public const string SystemPath = "system"; + public static string SafeNandPath = Path.Combine(NandPath, "safe"); public static string SystemNandPath = Path.Combine(NandPath, "system"); public static string UserNandPath = Path.Combine(NandPath, "user"); @@ -63,9 +65,15 @@ namespace Ryujinx.HLE.FileSystem return MakeDirAndGetFullPath(SaveHelper.GetSavePath(Save, Context)); } + public string GetFullPartitionPath(string PartitionPath) + { + return MakeDirAndGetFullPath(PartitionPath); + } + public string SwitchPathToSystemPath(string SwitchPath) { string[] Parts = SwitchPath.Split(":"); + if (Parts.Length != 2) { return null; @@ -76,10 +84,12 @@ namespace Ryujinx.HLE.FileSystem public string SystemPathToSwitchPath(string SystemPath) { string BaseSystemPath = GetBasePath() + Path.DirectorySeparatorChar; + if (SystemPath.StartsWith(BaseSystemPath)) { - string RawPath = SystemPath.Replace(BaseSystemPath, ""); - int FirstSeparatorOffset = RawPath.IndexOf(Path.DirectorySeparatorChar); + string RawPath = SystemPath.Replace(BaseSystemPath, ""); + int FirstSeparatorOffset = RawPath.IndexOf(Path.DirectorySeparatorChar); + if (FirstSeparatorOffset == -1) { return $"{RawPath}:/"; @@ -87,6 +97,7 @@ namespace Ryujinx.HLE.FileSystem string BasePath = RawPath.Substring(0, FirstSeparatorOffset); string FileName = RawPath.Substring(FirstSeparatorOffset + 1); + return $"{BasePath}:/{FileName}"; } return null; @@ -94,6 +105,30 @@ namespace Ryujinx.HLE.FileSystem private string MakeDirAndGetFullPath(string Dir) { + // Handles Common Switch Content Paths + switch (Dir) + { + case ContentPath.SdCard: + case "@Sdcard": + Dir = SdCardPath; + break; + case ContentPath.User: + Dir = UserNandPath; + break; + case ContentPath.System: + Dir = SystemNandPath; + break; + case ContentPath.SdCardContent: + Dir = Path.Combine(SdCardPath, "Nintendo", "Contents"); + break; + case ContentPath.UserContent: + Dir = Path.Combine(UserNandPath, "Contents"); + break; + case ContentPath.SystemContent: + Dir = Path.Combine(SystemNandPath, "Contents"); + break; + } + string FullPath = Path.Combine(GetBasePath(), Dir); if (!Directory.Exists(FullPath)) |