Implement aoc:u and support loading AddOnContent (#1221)

* Initial rebased AddOnContent support

* Fix bounds calculation
* Use existing GameCard in VFS per Xpl0itR's suggestion
+ Add dummy IPurchaseEventManager per AcK's suggestion

* Support multiple containers

* Add option to selectively disable addons

* Import tickets from AOC FS

* Load all nsps in base directory automatically

* Revert LoadNsp renaming

Removes conflicts with Mods PR. Not much is lost, old names were fine.

* Address AcK's comments

* Address Thog's comments

Dispose opened nsp files
Fix potential bug by clearing metadata on load
This commit is contained in:
mageven 2020-06-20 23:08:14 +05:30 committed by GitHub
parent 4d56f97f1e
commit 1c2af7ce92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 317 additions and 14 deletions

View file

@ -4,6 +4,8 @@ using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using LibHac.Ncm;
using LibHac.Spl;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Services.Time;
using Ryujinx.HLE.Utilities;
@ -28,6 +30,22 @@ namespace Ryujinx.HLE.FileSystem.Content
private SortedDictionary<(ulong titleId, NcaContentType type), string> _contentDictionary;
private struct AocItem
{
public readonly string ContainerPath;
public readonly string NcaPath;
public bool Enabled;
public AocItem(string containerPath, string ncaPath, bool enabled)
{
ContainerPath = containerPath;
NcaPath = ncaPath;
Enabled = enabled;
}
}
private SortedList<ulong, AocItem> _aocData { get; }
private VirtualFileSystem _virtualFileSystem;
private readonly object _lock = new object();
@ -68,6 +86,8 @@ namespace Ryujinx.HLE.FileSystem.Content
};
_virtualFileSystem = virtualFileSystem;
_aocData = new SortedList<ulong, AocItem>();
}
public void LoadEntries(Switch device = null)
@ -176,6 +196,89 @@ namespace Ryujinx.HLE.FileSystem.Content
}
}
// fs must contain AOC nca files in its root
public void AddAocData(IFileSystem fs, string containerPath, ulong aocBaseId)
{
_virtualFileSystem.ImportTickets(fs);
foreach (var ncaPath in fs.EnumerateEntries("*.cnmt.nca", SearchOptions.Default))
{
fs.OpenFile(out IFile ncaFile, ncaPath.FullPath.ToU8Span(), OpenMode.Read);
using (ncaFile)
{
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
if (nca.Header.ContentType != NcaContentType.Meta)
{
Logger.PrintWarning(LogClass.Application, $"{ncaPath} is not a valid metadata file");
continue;
}
using var pfs0 = nca.OpenFileSystem(0, Switch.GetIntegrityCheckLevel());
pfs0.OpenFile(out IFile cnmtFile, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read);
using (cnmtFile)
{
var cnmt = new Cnmt(cnmtFile.AsStream());
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
{
continue;
}
string ncaId = BitConverter.ToString(cnmt.ContentEntries[0].NcaId).Replace("-", "").ToLower();
if (!_aocData.TryAdd(cnmt.TitleId, new AocItem(containerPath, $"{ncaId}.nca", true)))
{
Logger.PrintWarning(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {cnmt.TitleId:X16}");
}
else
{
Logger.PrintInfo(LogClass.Application, $"Found AddOnContent with TitleId {cnmt.TitleId:X16}");
}
}
}
}
}
public void ClearAocData() => _aocData.Clear();
public int GetAocCount() => _aocData.Where(e => e.Value.Enabled).Count();
public IList<ulong> GetAocTitleIds() => _aocData.Where(e => e.Value.Enabled).Select(e => e.Key).ToList();
public bool GetAocDataStorage(ulong aocTitleId, out IStorage aocStorage)
{
aocStorage = null;
if (_aocData.TryGetValue(aocTitleId, out AocItem aoc) && aoc.Enabled)
{
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
PartitionFileSystem pfs;
IFile ncaFile;
switch (Path.GetExtension(aoc.ContainerPath))
{
case ".xci":
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
pfs.OpenFile(out ncaFile, aoc.NcaPath.ToU8Span(), OpenMode.Read);
break;
case ".nsp":
pfs = new PartitionFileSystem(file.AsStorage());
pfs.OpenFile(out ncaFile, aoc.NcaPath.ToU8Span(), OpenMode.Read);
break;
default:
return false; // Print error?
}
aocStorage = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage()).OpenStorage(NcaSectionType.Data, Switch.GetIntegrityCheckLevel());
return true;
}
return false;
}
public void ClearEntry(long titleId, NcaContentType contentType, StorageId storageId)
{
lock (_lock)