diff options
author | mageven <62494521+mageven@users.noreply.github.com> | 2020-06-20 23:08:14 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-20 19:38:14 +0200 |
commit | 1c2af7ce926bd6f48309247dc65012d410d77bee (patch) | |
tree | d9c280ebdd4bda537abe39e953de08f47c50101a /Ryujinx.HLE/FileSystem/Content/ContentManager.cs | |
parent | 4d56f97f1e62cf3d5a5de3088f5b755a7dcf5f51 (diff) |
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
Diffstat (limited to 'Ryujinx.HLE/FileSystem/Content/ContentManager.cs')
-rw-r--r-- | Ryujinx.HLE/FileSystem/Content/ContentManager.cs | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs index 22d97f3b..839078e8 100644 --- a/Ryujinx.HLE/FileSystem/Content/ContentManager.cs +++ b/Ryujinx.HLE/FileSystem/Content/ContentManager.cs @@ -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) |