diff --git a/.gitignore b/.gitignore index db96b630..a71fe14e 100644 --- a/.gitignore +++ b/.gitignore @@ -260,3 +260,6 @@ paket-files/ __pycache__/ *.pyc **/launchSettings.json + + +global.json \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index b61336a0..304207eb 100644 --- a/build.ps1 +++ b/build.ps1 @@ -45,7 +45,7 @@ try { } # If dotnet is installed locally, and expected version is not set or installation matches the expected version if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` - (!(Test-Path variable:DotNetVersion) -or $(& cmd /c 'dotnet --version 2>&1') -eq $DotNetVersion)) { + (!(Test-Path variable:DotNetVersion) -or $(& cmd.exe /c 'dotnet --version 2>&1') -eq $DotNetVersion)) { $env:DOTNET_EXE = (Get-Command "dotnet").Path } else { diff --git a/src/LibHac/Crypto.cs b/src/LibHac/Crypto.cs index e255f0c3..d5c0d8b1 100644 --- a/src/LibHac/Crypto.cs +++ b/src/LibHac/Crypto.cs @@ -121,21 +121,17 @@ namespace LibHac { var counter = new byte[0x10]; Array.Copy(encryptedKey, counter, 0x10); - var body = new byte[0x230]; - Array.Copy(encryptedKey, 0x10, body, 0, 0x230); - var dec = new byte[0x230]; + var key = new byte[0x230]; + Array.Copy(encryptedKey, 0x10, key, 0, 0x230); - using (var storageDec = new Aes128CtrStorage(new MemoryStorage(body), kek, counter, false)) - { - storageDec.Read(dec, 0); - } + new Aes128CtrTransform(kek, counter).TransformBlock(key); var d = new byte[0x100]; var n = new byte[0x100]; var e = new byte[4]; - Array.Copy(dec, 0, d, 0, 0x100); - Array.Copy(dec, 0x100, n, 0, 0x100); - Array.Copy(dec, 0x200, e, 0, 4); + Array.Copy(key, 0, d, 0, 0x100); + Array.Copy(key, 0x100, n, 0, 0x100); + Array.Copy(key, 0x200, e, 0, 4); BigInteger dInt = GetBigInteger(d); BigInteger nInt = GetBigInteger(n); diff --git a/src/LibHac/IO/Save/AllocationTable.cs b/src/LibHac/IO/Save/AllocationTable.cs index 5edaf7f6..6269912a 100644 --- a/src/LibHac/IO/Save/AllocationTable.cs +++ b/src/LibHac/IO/Save/AllocationTable.cs @@ -17,7 +17,9 @@ namespace LibHac.IO.Save Header = new AllocationTableHeader(HeaderStorage); Stream tableStream = storage.AsStream(); - int blockCount = (int)(Header.AllocationTableBlockCount); + + // The first entry in the table is reserved. Block 0 is at table index 1 + int blockCount = (int)(Header.AllocationTableBlockCount) + 1; Entries = new AllocationTableEntry[blockCount]; tableStream.Position = 0; diff --git a/src/LibHac/IO/StorageStream.cs b/src/LibHac/IO/StorageStream.cs deleted file mode 100644 index 787a2df4..00000000 --- a/src/LibHac/IO/StorageStream.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.IO; - -namespace LibHac.IO -{ - public class StorageStream : Stream - { - private IStorage BaseStorage { get; } - private bool LeaveOpen { get; } - - public StorageStream(IStorage baseStorage, bool leaveOpen) - { - BaseStorage = baseStorage; - LeaveOpen = leaveOpen; - Length = baseStorage.Length; - } - - public override int Read(byte[] buffer, int offset, int count) - { - int toRead = (int)Math.Min(count, Length - Position); - BaseStorage.Read(buffer, Position, toRead, offset); - - Position += toRead; - return toRead; - } - - public override void Write(byte[] buffer, int offset, int count) - { - BaseStorage.Write(buffer, Position, count, offset); - Position += count; - } - - public override void Flush() - { - BaseStorage.Flush(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - Position = offset; - break; - case SeekOrigin.Current: - Position += offset; - break; - case SeekOrigin.End: - Position = Length - offset; - break; - } - - return Position; - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override bool CanRead => (BaseStorage as Storage)?.CanRead ?? true; - public override bool CanSeek => true; - public override bool CanWrite => (BaseStorage as Storage)?.CanWrite ?? true; - - public override long Length { get; } - public override long Position { get; set; } - - protected override void Dispose(bool disposing) - { - if (!LeaveOpen) BaseStorage?.Dispose(); - base.Dispose(disposing); - } - } -} 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/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 e5218f11..87788505 100644 --- a/src/hactoolnet/ProcessSwitchFs.cs +++ b/src/hactoolnet/ProcessSwitchFs.cs @@ -15,9 +15,14 @@ namespace hactoolnet { var switchFs = new SwitchFs(ctx.Keyset, new LocalFileSystem(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