aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/FileSystem
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.HLE/FileSystem')
-rw-r--r--Ryujinx.HLE/FileSystem/Content/ContentManager.cs300
-rw-r--r--Ryujinx.HLE/FileSystem/Content/ContentPath.cs19
-rw-r--r--Ryujinx.HLE/FileSystem/Content/LocationEntry.cs28
-rw-r--r--Ryujinx.HLE/FileSystem/Content/LocationHelper.cs91
-rw-r--r--Ryujinx.HLE/FileSystem/Content/StorageId.cs9
-rw-r--r--Ryujinx.HLE/FileSystem/Content/TitleType.cs15
-rw-r--r--Ryujinx.HLE/FileSystem/FileSystemProvider.cs281
-rw-r--r--Ryujinx.HLE/FileSystem/IFileSystemProvider.cs41
-rw-r--r--Ryujinx.HLE/FileSystem/PFsProvider.cs146
-rw-r--r--Ryujinx.HLE/FileSystem/RomFsProvider.cs163
-rw-r--r--Ryujinx.HLE/FileSystem/SaveSpaceId.cs2
-rw-r--r--Ryujinx.HLE/FileSystem/StorageId.cs12
-rw-r--r--Ryujinx.HLE/FileSystem/VirtualFileSystem.cs39
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))