From ca815312d3a2c7cf384aba42f2365fd02c535b8b Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 2 Jul 2018 13:16:38 -0500 Subject: [PATCH] Group titles by main application --- hactoolnet/Program.cs | 36 ++++++++++++++++++++ libhac/Cnmt.cs | 1 + libhac/Nacp.cs | 23 +++++++++++++ libhac/SdFs.cs | 77 ++++++++++++++++++++++++++++++++++--------- 4 files changed, 122 insertions(+), 15 deletions(-) diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index 377c98b8..edd5a646 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -27,6 +27,9 @@ namespace hactoolnet Console.WriteLine("Listing titles"); ListTitles(sdfs); + Console.WriteLine("Listing applications"); + ListApplications(sdfs); + //DecryptNax0(sdfs, "C0628FB07A89E9050BDA258F74868E8D"); //DecryptTitle(sdfs, 0x010023900AEE0000); } @@ -151,5 +154,38 @@ namespace hactoolnet Console.WriteLine(""); } } + + static void ListApplications(SdFs sdfs) + { + foreach (var app in sdfs.Applications.Values.OrderBy(x => x.Name)) + { + Console.WriteLine($"{app.Name} v{app.DisplayVersion}"); + + long totalSize = 0; + if (app.Main != null) + { + Console.WriteLine($"Software: {Util.GetBytesReadable(app.Main.GetSize())}"); + } + + if (app.Patch != null) + { + Console.WriteLine($"Update Data: {Util.GetBytesReadable(app.Patch.GetSize())}"); + } + + if (app.AddOnContent.Count > 0) + { + Console.WriteLine($"DLC: {Util.GetBytesReadable(app.AddOnContent.Sum(x => x.GetSize()))}"); + } + + if (app.Nacp?.UserTotalSaveDataSize > 0) + Console.WriteLine($"User save: {Util.GetBytesReadable(app.Nacp.UserTotalSaveDataSize)}"); + if (app.Nacp?.DeviceTotalSaveDataSize > 0) + Console.WriteLine($"System save: {Util.GetBytesReadable(app.Nacp.DeviceTotalSaveDataSize)}"); + if (app.Nacp?.BcatSaveDataSize > 0) + Console.WriteLine($"BCAT save: {Util.GetBytesReadable(app.Nacp.BcatSaveDataSize)}"); + + Console.WriteLine(""); + } + } } } diff --git a/libhac/Cnmt.cs b/libhac/Cnmt.cs index bec3c181..2284985e 100644 --- a/libhac/Cnmt.cs +++ b/libhac/Cnmt.cs @@ -37,6 +37,7 @@ namespace libhac switch (Type) { case TitleType.Application: + ApplicationTitleId = TitleId; PatchTitleId = reader.ReadUInt64(); MinimumSystemVersion = new TitleVersion(reader.ReadUInt32(), true); break; diff --git a/libhac/Nacp.cs b/libhac/Nacp.cs index 3af5843a..4b676430 100644 --- a/libhac/Nacp.cs +++ b/libhac/Nacp.cs @@ -6,6 +6,17 @@ namespace libhac { public NacpLang[] Languages { get; } = new NacpLang[0x10]; public string Version { get; } + public ulong AddOnContentBaseId { get; } + public ulong SaveDataOwnerId { get; } + public long UserAccountSaveDataSize { get; } + public long UserAccountSaveDataJournalSize { get; } + public long DeviceSaveDataSize { get; } + public long DeviceSaveDataJournalSize { get; } + public long BcatSaveDataSize { get; } + + public long TotalSaveDataSize { get; } + public long UserTotalSaveDataSize { get; } + public long DeviceTotalSaveDataSize { get; } public Nacp(BinaryReader reader) { @@ -18,6 +29,18 @@ namespace libhac reader.BaseStream.Position = start + 0x3060; Version = reader.ReadUtf8Z(); + reader.BaseStream.Position = start + 0x3070; + AddOnContentBaseId = reader.ReadUInt64(); + SaveDataOwnerId = reader.ReadUInt64(); + UserAccountSaveDataSize = reader.ReadInt64(); + UserAccountSaveDataJournalSize = reader.ReadInt64(); + DeviceSaveDataSize = reader.ReadInt64(); + DeviceSaveDataJournalSize = reader.ReadInt64(); + BcatSaveDataSize = reader.ReadInt64(); + + UserTotalSaveDataSize = UserAccountSaveDataSize + UserAccountSaveDataJournalSize; + DeviceTotalSaveDataSize = DeviceSaveDataSize + DeviceSaveDataJournalSize; + TotalSaveDataSize = UserTotalSaveDataSize + DeviceTotalSaveDataSize; } } diff --git a/libhac/SdFs.cs b/libhac/SdFs.cs index b4f959bd..33aaf025 100644 --- a/libhac/SdFs.cs +++ b/libhac/SdFs.cs @@ -15,6 +15,7 @@ namespace libhac public Dictionary Ncas { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public Dictionary Titles { get; } = new Dictionary(); + public Dictionary Applications { get; } = new Dictionary(); private List Nax0s { get; } = new List(); @@ -35,6 +36,7 @@ namespace libhac OpenAllNcas(); ReadTitles(); ReadControls(); + CreateApplications(); } private void OpenAllNcas() @@ -68,7 +70,7 @@ namespace libhac { nca = new Nca(Keyset, stream, false); } - + nca.NcaId = Path.GetFileNameWithoutExtension(file); var extention = nca.Header.ContentType == ContentType.Meta ? ".cnmt.nca" : ".nca"; nca.Filename = nca.NcaId + extention; @@ -147,6 +149,23 @@ namespace libhac } } + private void CreateApplications() + { + foreach (var title in Titles.Values.Where(x => x.Metadata.Type >= TitleType.Application)) + { + var meta = title.Metadata; + ulong appId = meta.ApplicationTitleId; + + if (!Applications.TryGetValue(appId, out var app)) + { + app = new Application(); + Applications.Add(appId, app); + } + + app.AddTitle(title); + } + } + internal static Stream OpenSplitNcaStream(string path) { List files = new List(); @@ -218,44 +237,72 @@ namespace libhac public Nca MetaNca { get; internal set; } public Nca ProgramNca { get; internal set; } public Nca ControlNca { get; internal set; } + + public long GetSize() + { + return Metadata.ContentEntries + .Where(x => x.Type < CnmtContentType.UpdatePatch) + .Sum(x => x.Size); + } } public class Application { public Title Main { get; private set; } public Title Patch { get; private set; } - public List AddOnContent { get; private set; } + public List<Title> AddOnContent { get; } = new List<Title>(); + + public ulong TitleId { get; private set; } + public TitleVersion Version { get; private set; } + public Nacp Nacp { get; private set; } public string Name { get; private set; } - public string Version { get; private set; } + public string DisplayVersion { get; private set; } - public void SetMainTitle(Title title) + public void AddTitle(Title title) { - Main = title; + if (TitleId != 0 && title.Metadata.ApplicationTitleId != TitleId) + throw new InvalidDataException("Title IDs do not match"); + TitleId = title.Metadata.ApplicationTitleId; + + switch (title.Metadata.Type) + { + case TitleType.Application: + Main = title; + break; + case TitleType.Patch: + Patch = title; + break; + case TitleType.AddOnContent: + AddOnContent.Add(title); + break; + case TitleType.DeltaTitle: + break; + } + + UpdateInfo(); } - public void SetPatchTitle(Title title) - { - if (title.Metadata.Type != TitleType.Patch) throw new InvalidDataException("Title is not a patch"); - Patch = title; - } - - private void UpdateName() + private void UpdateInfo() { if (Patch != null) { Name = Patch.Name; - Version = Patch.Control?.Version ?? ""; + Version = Patch.Version; + DisplayVersion = Patch.Control?.Version ?? ""; + Nacp = Patch.Control; } else if (Main != null) { Name = Main.Name; - Version = Main.Control?.Version ?? ""; + Version = Main.Version; + DisplayVersion = Main.Control?.Version ?? ""; + Nacp = Main.Control; } else { Name = ""; - Version = ""; + DisplayVersion = ""; } } }