2018-11-18 19:37:41 +00:00
|
|
|
|
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
|
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
private Dictionary<StorageId, LinkedList<LocationEntry>> _locationEntries;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
private Dictionary<string, long> _sharedFontTitleDictionary;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
private SortedDictionary<(ulong, ContentType), string> _contentDictionary;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
private Switch _device;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
public ContentManager(Switch device)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
_contentDictionary = new SortedDictionary<(ulong, ContentType), string>();
|
|
|
|
|
_locationEntries = new Dictionary<StorageId, LinkedList<LocationEntry>>();
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
_sharedFontTitleDictionary = new Dictionary<string, long>
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
|
|
|
|
{ "FontStandard", 0x0100000000000811 },
|
|
|
|
|
{ "FontChineseSimplified", 0x0100000000000814 },
|
|
|
|
|
{ "FontExtendedChineseSimplified", 0x0100000000000814 },
|
|
|
|
|
{ "FontKorean", 0x0100000000000812 },
|
|
|
|
|
{ "FontChineseTraditional", 0x0100000000000813 },
|
2018-12-04 20:23:37 +00:00
|
|
|
|
{ "FontNintendoExtended", 0x0100000000000810 }
|
2018-11-18 19:37:41 +00:00
|
|
|
|
};
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
_device = device;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadEntries()
|
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
_contentDictionary = new SortedDictionary<(ulong, ContentType), string>();
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
foreach (StorageId storageId in Enum.GetValues(typeof(StorageId)))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
string contentDirectory = null;
|
|
|
|
|
string contentPathString = null;
|
|
|
|
|
string registeredDirectory = null;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
contentPathString = LocationHelper.GetContentRoot(storageId);
|
|
|
|
|
contentDirectory = LocationHelper.GetRealPath(_device.FileSystem, contentPathString);
|
|
|
|
|
registeredDirectory = Path.Combine(contentDirectory, "registered");
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
2018-12-04 20:23:37 +00:00
|
|
|
|
catch (NotSupportedException)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
Directory.CreateDirectory(registeredDirectory);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LinkedList<LocationEntry> locationList = new LinkedList<LocationEntry>();
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
void AddEntry(LocationEntry entry)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
locationList.AddLast(entry);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
foreach (string directoryPath in Directory.EnumerateDirectories(registeredDirectory))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (Directory.GetFiles(directoryPath).Length > 0)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
string ncaName = new DirectoryInfo(directoryPath).Name.Replace(".nca", string.Empty);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
using (FileStream ncaFile = new FileStream(Directory.GetFiles(directoryPath)[0], FileMode.Open, FileAccess.Read))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
Nca nca = new Nca(_device.System.KeySet, ncaFile, false);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
string switchPath = Path.Combine(contentPathString + ":",
|
|
|
|
|
ncaFile.Name.Replace(contentDirectory, string.Empty).TrimStart('\\'));
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
|
|
|
|
// Change path format to switch's
|
2018-12-04 20:23:37 +00:00
|
|
|
|
switchPath = switchPath.Replace('\\', '/');
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LocationEntry entry = new LocationEntry(switchPath,
|
2018-11-18 19:37:41 +00:00
|
|
|
|
0,
|
2018-12-04 20:23:37 +00:00
|
|
|
|
(long)nca.Header.TitleId,
|
|
|
|
|
nca.Header.ContentType);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
AddEntry(entry);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
_contentDictionary.Add((nca.Header.TitleId, nca.Header.ContentType), ncaName);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
ncaFile.Close();
|
|
|
|
|
nca.Dispose();
|
|
|
|
|
ncaFile.Dispose();
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
foreach (string filePath in Directory.EnumerateFiles(contentDirectory))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (Path.GetExtension(filePath) == ".nca")
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
string ncaName = Path.GetFileNameWithoutExtension(filePath);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
using (FileStream ncaFile = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
Nca nca = new Nca(_device.System.KeySet, ncaFile, false);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
string switchPath = Path.Combine(contentPathString + ":",
|
|
|
|
|
filePath.Replace(contentDirectory, string.Empty).TrimStart('\\'));
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
|
|
|
|
// Change path format to switch's
|
2018-12-04 20:23:37 +00:00
|
|
|
|
switchPath = switchPath.Replace('\\', '/');
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LocationEntry entry = new LocationEntry(switchPath,
|
2018-11-18 19:37:41 +00:00
|
|
|
|
0,
|
2018-12-04 20:23:37 +00:00
|
|
|
|
(long)nca.Header.TitleId,
|
|
|
|
|
nca.Header.ContentType);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
AddEntry(entry);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
_contentDictionary.Add((nca.Header.TitleId, nca.Header.ContentType), ncaName);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
ncaFile.Close();
|
|
|
|
|
nca.Dispose();
|
|
|
|
|
ncaFile.Dispose();
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if(_locationEntries.ContainsKey(storageId) && _locationEntries[storageId]?.Count == 0)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
_locationEntries.Remove(storageId);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (!_locationEntries.ContainsKey(storageId))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
_locationEntries.Add(storageId, locationList);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
public void ClearEntry(long titleId, ContentType contentType,StorageId storageId)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
RemoveLocationEntry(titleId, contentType, storageId);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
public void RefreshEntries(StorageId storageId, int flag)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LinkedList<LocationEntry> locationList = _locationEntries[storageId];
|
|
|
|
|
LinkedListNode<LocationEntry> locationEntry = locationList.First;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
while (locationEntry != null)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LinkedListNode<LocationEntry> nextLocationEntry = locationEntry.Next;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (locationEntry.Value.Flag == flag)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
locationList.Remove(locationEntry.Value);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
locationEntry = nextLocationEntry;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
public bool HasNca(string ncaId, StorageId storageId)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (_contentDictionary.ContainsValue(ncaId))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
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);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
return storage == storageId;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
public UInt128 GetInstalledNcaId(long titleId, ContentType contentType)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (_contentDictionary.ContainsKey(((ulong)titleId,contentType)))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
return new UInt128(_contentDictionary[((ulong)titleId,contentType)]);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new UInt128();
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
public StorageId GetInstalledStorage(long titleId, ContentType contentType, StorageId storageId)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LocationEntry locationEntry = GetLocation(titleId, contentType, storageId);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
return locationEntry.ContentPath != null ?
|
|
|
|
|
LocationHelper.GetStorageId(locationEntry.ContentPath) : StorageId.None;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
public string GetInstalledContentPath(long titleId, StorageId storageId, ContentType contentType)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LocationEntry locationEntry = GetLocation(titleId, contentType, storageId);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (VerifyContentType(locationEntry, contentType))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
return locationEntry.ContentPath;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
public void RedirectLocation(LocationEntry newEntry, StorageId storageId)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LocationEntry locationEntry = GetLocation(newEntry.TitleId, newEntry.ContentType, storageId);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (locationEntry.ContentPath != null)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
RemoveLocationEntry(newEntry.TitleId, newEntry.ContentType, storageId);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
AddLocationEntry(newEntry, storageId);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
private bool VerifyContentType(LocationEntry locationEntry, ContentType contentType)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (locationEntry.ContentPath == null)
|
2018-11-19 00:20:17 +00:00
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
StorageId storageId = LocationHelper.GetStorageId(locationEntry.ContentPath);
|
|
|
|
|
string installedPath = _device.FileSystem.SwitchPathToSystemPath(locationEntry.ContentPath);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (!string.IsNullOrWhiteSpace(installedPath))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (File.Exists(installedPath))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
FileStream file = new FileStream(installedPath, FileMode.Open, FileAccess.Read);
|
|
|
|
|
Nca nca = new Nca(_device.System.KeySet, file, false);
|
|
|
|
|
bool contentCheck = nca.Header.ContentType == contentType;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
nca.Dispose();
|
|
|
|
|
file.Dispose();
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
return contentCheck;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
private void AddLocationEntry(LocationEntry entry, StorageId storageId)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LinkedList<LocationEntry> locationList = null;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (_locationEntries.ContainsKey(storageId))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
locationList = _locationEntries[storageId];
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (locationList != null)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (locationList.Contains(entry))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
locationList.Remove(entry);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
locationList.AddLast(entry);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
private void RemoveLocationEntry(long titleId, ContentType contentType, StorageId storageId)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LinkedList<LocationEntry> locationList = null;
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (_locationEntries.ContainsKey(storageId))
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
locationList = _locationEntries[storageId];
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (locationList != null)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LocationEntry entry =
|
|
|
|
|
locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
if (entry.ContentPath != null)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
locationList.Remove(entry);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
public bool TryGetFontTitle(string fontName, out long titleId)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
return _sharedFontTitleDictionary.TryGetValue(fontName, out titleId);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
private LocationEntry GetLocation(long titleId, ContentType contentType,StorageId storageId)
|
2018-11-18 19:37:41 +00:00
|
|
|
|
{
|
2018-12-04 20:23:37 +00:00
|
|
|
|
LinkedList<LocationEntry> locationList = _locationEntries[storageId];
|
2018-11-18 19:37:41 +00:00
|
|
|
|
|
2018-12-04 20:23:37 +00:00
|
|
|
|
return locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType);
|
2018-11-18 19:37:41 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|