diff --git a/src/LibHac/NcaStructs.cs b/src/LibHac/NcaStructs.cs index 81120717..7f059f76 100644 --- a/src/LibHac/NcaStructs.cs +++ b/src/LibHac/NcaStructs.cs @@ -13,7 +13,7 @@ namespace LibHac public ContentType ContentType; public byte CryptoType; // Which keyblob (field 1) public byte KaekInd; // Which kaek index? - public ulong NcaSize; // Entire archive size. + public long NcaSize; // Entire archive size. public ulong TitleId; public TitleVersion SdkVersion; // What SDK was this built with? public byte CryptoType2; // Which keyblob (field 2) @@ -46,7 +46,7 @@ namespace LibHac ContentType = (ContentType)reader.ReadByte(); CryptoType = reader.ReadByte(); KaekInd = reader.ReadByte(); - NcaSize = reader.ReadUInt64(); + NcaSize = reader.ReadInt64(); TitleId = reader.ReadUInt64(); reader.BaseStream.Position += 4; diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index 31e84fc9..59a49785 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -66,7 +66,7 @@ namespace LibHac try { bool isNax0; - IStorage storage = OpenSplitNcaStream(Fs, file); + IStorage storage = OpenSplitNcaStorage(Fs, file); if (storage == null) continue; using (var reader = new BinaryReader(storage.AsStream(), Encoding.Default, true)) @@ -232,7 +232,7 @@ namespace LibHac } } - internal static IStorage OpenSplitNcaStream(IFileSystem fs, string path) + internal static IStorage OpenSplitNcaStorage(IFileSystem fs, string path) { var files = new List(); var storages = new List(); @@ -307,9 +307,7 @@ namespace LibHac public long GetSize() { - return Metadata.ContentEntries - .Where(x => x.Type < CnmtContentType.DeltaFragment) - .Sum(x => x.Size); + return Ncas.Sum(x => x.Header.NcaSize); } } diff --git a/src/hactoolnet/CliParser.cs b/src/hactoolnet/CliParser.cs index f9a9b934..13838592 100644 --- a/src/hactoolnet/CliParser.cs +++ b/src/hactoolnet/CliParser.cs @@ -44,6 +44,7 @@ namespace hactoolnet new CliOption("logodir", 1, (o, a) => o.LogoDir = a[0]), new CliOption("listapps", 0, (o, a) => o.ListApps = true), new CliOption("listtitles", 0, (o, a) => o.ListTitles = true), + new CliOption("listncas", 0, (o, a) => o.ListNcas = true), new CliOption("listromfs", 0, (o, a) => o.ListRomFs = true), new CliOption("listfiles", 0, (o, a) => o.ListFiles = true), new CliOption("sign", 0, (o, a) => o.SignSave = true), @@ -194,6 +195,7 @@ namespace hactoolnet sb.AppendLine(" --sdseed Set console unique seed for SD card NAX0 encryption."); sb.AppendLine(" --listapps List application info."); sb.AppendLine(" --listtitles List title info for all titles."); + sb.AppendLine(" --listncas List info for all NCAs."); sb.AppendLine(" --title Specify title ID to use."); sb.AppendLine(" --outdir <dir> Specify directory path to save title NCAs to. (--title must be specified)"); sb.AppendLine(" --exefs <file> Specify ExeFS directory path. (--title must be specified)"); diff --git a/src/hactoolnet/Options.cs b/src/hactoolnet/Options.cs index 911c30de..6efce3d7 100644 --- a/src/hactoolnet/Options.cs +++ b/src/hactoolnet/Options.cs @@ -35,6 +35,7 @@ namespace hactoolnet public string LogoDir; public bool ListApps; public bool ListTitles; + public bool ListNcas; public bool ListRomFs; public bool ListFiles; public bool SignSave; diff --git a/src/hactoolnet/ProcessSwitchFs.cs b/src/hactoolnet/ProcessSwitchFs.cs index debaca6e..4d1136fe 100644 --- a/src/hactoolnet/ProcessSwitchFs.cs +++ b/src/hactoolnet/ProcessSwitchFs.cs @@ -15,9 +15,14 @@ namespace hactoolnet { var switchFs = new SwitchFs(ctx.Keyset, new FileSystem(ctx.Options.InFile)); + if (ctx.Options.ListNcas) + { + ctx.Logger.LogMessage(ListNcas(switchFs)); + } + if (ctx.Options.ListTitles) { - ListTitles(switchFs); + ctx.Logger.LogMessage(ListTitles(switchFs)); } if (ctx.Options.ListApps) @@ -213,31 +218,34 @@ namespace hactoolnet } } - static void ListTitles(SwitchFs sdfs) + static string ListTitles(SwitchFs sdfs) { + var table = new TableBuilder("Title ID", "Version", "", "Type", "Size", "Display Version", "Name"); + foreach (Title title in sdfs.Titles.Values.OrderBy(x => x.Id)) { - Console.WriteLine($"{title.Name} {title.Control?.DisplayVersion}"); - Console.WriteLine($"{title.Id:X16} v{title.Version.Version} ({title.Version}) {title.Metadata.Type}"); - - foreach (CnmtContentEntry content in title.Metadata.ContentEntries) - { - Console.WriteLine( - $" {content.NcaId.ToHexString()}.nca {content.Type} {Util.GetBytesReadable(content.Size)}"); - } - - foreach (Nca nca in title.Ncas) - { - Console.WriteLine($" {nca.HasRightsId} {nca.NcaId} {nca.Header.ContentType}"); - - foreach (NcaSection sect in nca.Sections.Where(x => x != null)) - { - Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.EncryptionType} {sect.MasterHashValidity}"); - } - } - - Console.WriteLine(""); + table.AddRow($"{title.Id:X16}", + $"v{title.Version?.Version}", + title.Version?.ToString(), + title.Metadata?.Type.ToString(), + Util.GetBytesReadable(title.GetSize()), + title.Control?.DisplayVersion, + title.Name); } + + return table.Print(); + } + + static string ListNcas(SwitchFs sdfs) + { + var table = new TableBuilder("NCA ID", "Type", "Title ID"); + + foreach (Nca nca in sdfs.Ncas.Values.OrderBy(x => x.NcaId)) + { + table.AddRow(nca.NcaId, nca.Header.ContentType.ToString(), nca.Header.TitleId.ToString("X16")); + } + + return table.Print(); } static string ListApplications(SwitchFs sdfs) diff --git a/src/hactoolnet/Program.cs b/src/hactoolnet/Program.cs index 5c6eb342..51b62f7d 100644 --- a/src/hactoolnet/Program.cs +++ b/src/hactoolnet/Program.cs @@ -149,7 +149,7 @@ namespace hactoolnet // ReSharper disable once UnusedParameter.Local private static void CustomTask(Context ctx) { - + } } } diff --git a/src/hactoolnet/TableBuilder.cs b/src/hactoolnet/TableBuilder.cs new file mode 100644 index 00000000..01cd74ac --- /dev/null +++ b/src/hactoolnet/TableBuilder.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace hactoolnet +{ + public class TableBuilder + { + private List<string[]> Rows { get; } = new List<string[]>(); + private int ColumnCount { get; set; } + + public TableBuilder(params string[] header) + { + ColumnCount = header.Length; + Rows.Add(header); + } + + public TableBuilder(int columnCount) + { + ColumnCount = columnCount; + } + + public void AddRow(params string[] row) + { + if (row.Length != ColumnCount) + { + throw new ArgumentOutOfRangeException(nameof(row), "All rows must have the same number of columns"); + } + + Rows.Add(row); + } + + public string Print() + { + var sb = new StringBuilder(); + var width = new int[ColumnCount]; + + foreach (string[] row in Rows) + { + for (int i = 0; i < ColumnCount - 1; i++) + { + width[i] = Math.Max(width[i], row[i]?.Length ?? 0); + } + } + + foreach (string[] row in Rows) + { + for (int i = 0; i < ColumnCount; i++) + { + sb.Append($"{(row[i] ?? string.Empty).PadRight(width[i] + 1, ' ')}"); + } + + sb.AppendLine(); + } + + return sb.ToString(); + } + } +} \ No newline at end of file