aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/FileSystem/Content/ContentManager.cs
diff options
context:
space:
mode:
authormageven <62494521+mageven@users.noreply.github.com>2020-06-20 23:08:14 +0530
committerGitHub <noreply@github.com>2020-06-20 19:38:14 +0200
commit1c2af7ce926bd6f48309247dc65012d410d77bee (patch)
treed9c280ebdd4bda537abe39e953de08f47c50101a /Ryujinx.HLE/FileSystem/Content/ContentManager.cs
parent4d56f97f1e62cf3d5a5de3088f5b755a7dcf5f51 (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.cs103
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)