Implement ContentManager and related services (#438)
* Implement contentmanager and related services * small changes * read system firmware version from nand * add pfs support, write directoryentry info for romfs files * add file check in fsp-srv:8 * add support for open fs of internal files * fix filename when accessing pfs * use switch style paths for contentpath * close nca after verifying type * removed publishing profiles, align directory entry * fix style * lots of style fixes * yasf(yet another style fix) * yasf(yet another style fix) plus symbols * enforce path check on every fs access * change enum type to default * fix typo
This commit is contained in:
parent
e603b7afbc
commit
fe8fbb6fb9
38 changed files with 2179 additions and 173 deletions
300
Ryujinx.HLE/FileSystem/Content/ContentManager.cs
Normal file
300
Ryujinx.HLE/FileSystem/Content/ContentManager.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
19
Ryujinx.HLE/FileSystem/Content/ContentPath.cs
Normal file
19
Ryujinx.HLE/FileSystem/Content/ContentPath.cs
Normal file
|
@ -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";
|
||||
}
|
||||
}
|
28
Ryujinx.HLE/FileSystem/Content/LocationEntry.cs
Normal file
28
Ryujinx.HLE/FileSystem/Content/LocationEntry.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
91
Ryujinx.HLE/FileSystem/Content/LocationHelper.cs
Normal file
91
Ryujinx.HLE/FileSystem/Content/LocationHelper.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
Ryujinx.HLE/FileSystem/Content/StorageId.cs
Normal file
9
Ryujinx.HLE/FileSystem/Content/StorageId.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.FileSystem.Content
|
||||
{
|
||||
public enum ContentStorageId
|
||||
{
|
||||
NandSystem,
|
||||
NandUser,
|
||||
SdCard
|
||||
}
|
||||
}
|
15
Ryujinx.HLE/FileSystem/Content/TitleType.cs
Normal file
15
Ryujinx.HLE/FileSystem/Content/TitleType.cs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
281
Ryujinx.HLE/FileSystem/FileSystemProvider.cs
Normal file
281
Ryujinx.HLE/FileSystem/FileSystemProvider.cs
Normal file
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
41
Ryujinx.HLE/FileSystem/IFileSystemProvider.cs
Normal file
41
Ryujinx.HLE/FileSystem/IFileSystemProvider.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
146
Ryujinx.HLE/FileSystem/PFsProvider.cs
Normal file
146
Ryujinx.HLE/FileSystem/PFsProvider.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
163
Ryujinx.HLE/FileSystem/RomFsProvider.cs
Normal file
163
Ryujinx.HLE/FileSystem/RomFsProvider.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
enum SaveSpaceId : byte
|
||||
enum SaveSpaceId
|
||||
{
|
||||
NandSystem,
|
||||
NandUser,
|
||||
|
|
12
Ryujinx.HLE/FileSystem/StorageId.cs
Normal file
12
Ryujinx.HLE/FileSystem/StorageId.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
internal enum StorageId
|
||||
{
|
||||
None,
|
||||
Host,
|
||||
GameCard,
|
||||
NandSystem,
|
||||
NandUser,
|
||||
SdCard
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue