From da9ecaec9811582a441fcb336194a9e6578c905d Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 9 Dec 2018 14:44:16 -0600 Subject: [PATCH 1/5] Fix build not detecting dotnet version --- .gitignore | 3 +++ build.ps1 | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) 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 { From ac6d528a301f9cc74453b639ce89f5b5acdcc6ee Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 9 Dec 2018 18:23:28 -0600 Subject: [PATCH 2/5] Make StorageStream check the read/write status of base Storages --- src/LibHac/IO/StorageStream.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LibHac/IO/StorageStream.cs b/src/LibHac/IO/StorageStream.cs index b69ad781..c25cd809 100644 --- a/src/LibHac/IO/StorageStream.cs +++ b/src/LibHac/IO/StorageStream.cs @@ -58,9 +58,9 @@ namespace LibHac.IO throw new NotImplementedException(); } - public override bool CanRead => true; + public override bool CanRead => (BaseStorage as Storage)?.CanRead ?? true; public override bool CanSeek => true; - public override bool CanWrite => true; + public override bool CanWrite => (BaseStorage as Storage)?.CanWrite ?? true; public override long Length { get; } public override long Position { get; set; } From 5a15118706347647960086bd4b143b7902103e6b Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 9 Dec 2018 18:23:46 -0600 Subject: [PATCH 3/5] Use Aes128CtrTransform in DecryptRsaKey --- src/LibHac/Crypto.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/LibHac/Crypto.cs b/src/LibHac/Crypto.cs index 03b38622..1c0aaf48 100644 --- a/src/LibHac/Crypto.cs +++ b/src/LibHac/Crypto.cs @@ -106,21 +106,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); From fa91eea11d3e1b1ab2f684d0e6a45abecf7ea039 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 10 Dec 2018 15:00:20 -0600 Subject: [PATCH 4/5] Modify Switch FS print options --- src/LibHac/NcaStructs.cs | 4 +-- src/LibHac/SwitchFs.cs | 8 ++--- src/hactoolnet/CliParser.cs | 2 ++ src/hactoolnet/Options.cs | 1 + src/hactoolnet/ProcessSwitchFs.cs | 52 +++++++++++++++------------ src/hactoolnet/Program.cs | 2 +- src/hactoolnet/TableBuilder.cs | 59 +++++++++++++++++++++++++++++++ 7 files changed, 98 insertions(+), 30 deletions(-) create mode 100644 src/hactoolnet/TableBuilder.cs 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 From 4a65a5da2045f19d249943c7e2e207456b2f29cb Mon Sep 17 00:00:00 2001 From: Alex Barney <thealexbarney@gmail.com> Date: Mon, 10 Dec 2018 16:22:51 -0600 Subject: [PATCH 5/5] Fix save data off-by-one error --- src/LibHac/IO/Save/AllocationTable.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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;