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:
emmauss 2018-11-18 21:37:41 +02:00 committed by gdkchan
parent e603b7afbc
commit fe8fbb6fb9
38 changed files with 2179 additions and 173 deletions

View 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);
}
}
}

View 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";
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.FileSystem.Content
{
public enum ContentStorageId
{
NandSystem,
NandUser,
SdCard
}
}

View 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
}
}