From 1c2af7ce926bd6f48309247dc65012d410d77bee Mon Sep 17 00:00:00 2001
From: mageven <62494521+mageven@users.noreply.github.com>
Date: Sat, 20 Jun 2020 23:08:14 +0530
Subject: 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
---
 Ryujinx.HLE/FileSystem/Content/ContentManager.cs | 103 +++++++++++++++++++++++
 1 file changed, 103 insertions(+)

(limited to 'Ryujinx.HLE/FileSystem/Content/ContentManager.cs')

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)
-- 
cgit v1.2.3-70-g09d2