diff --git a/src/LibHac/IO/NcaUtils/Nca.cs b/src/LibHac/IO/NcaUtils/Nca.cs index b72c0031..5f4c7118 100644 --- a/src/LibHac/IO/NcaUtils/Nca.cs +++ b/src/LibHac/IO/NcaUtils/Nca.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using LibHac.IO.RomFs; @@ -7,6 +8,9 @@ namespace LibHac.IO.NcaUtils { public class Nca : IDisposable { + private const int HeaderSize = 0xc00; + private const int HeaderSectorSize = 0x200; + public NcaHeader Header { get; } public string NcaId { get; set; } public string Filename { get; set; } @@ -334,9 +338,9 @@ namespace LibHac.IO.NcaUtils return new NcaHeader(new BinaryReader(OpenHeaderStorage().AsStream()), Keyset); } - private CachedStorage OpenHeaderStorage() + public IStorage OpenHeaderStorage() { - long size = 0xc00; + long size = HeaderSize; // Encrypted portion continues until the first section if (Sections.Any(x => x != null)) @@ -344,7 +348,42 @@ namespace LibHac.IO.NcaUtils size = Sections.Where(x => x != null).Min(x => x.Offset); } - return new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, size), Keyset.HeaderKey, 0x200, true), 1, true); + IStorage header = new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, size), Keyset.HeaderKey, HeaderSectorSize, true), 1, true); + int version = ReadHeaderVersion(header); + + if (version == 2) + { + header = OpenNca2Header(size); + } + + return header; + } + + private int ReadHeaderVersion(IStorage header) + { + if (Header != null) + { + return Header.Version; + } + else + { + Span buf = stackalloc byte[1]; + header.Read(buf, 0x203); + return buf[0] - '0'; + } + } + + private IStorage OpenNca2Header(long size) + { + var sources = new List(); + sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), Keyset.HeaderKey, HeaderSectorSize, true), 1, true)); + + for (int i = 0x400; i < size; i += HeaderSectorSize) + { + sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(i, HeaderSectorSize), Keyset.HeaderKey, HeaderSectorSize, true), 1, true)); + } + + return new ConcatenationStorage(sources, true); } private void DecryptKeyArea(Keyset keyset) diff --git a/src/LibHac/IO/NcaUtils/NcaStructs.cs b/src/LibHac/IO/NcaUtils/NcaStructs.cs index c1c34095..07d6a731 100644 --- a/src/LibHac/IO/NcaUtils/NcaStructs.cs +++ b/src/LibHac/IO/NcaUtils/NcaStructs.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; namespace LibHac.IO.NcaUtils { @@ -7,6 +8,7 @@ namespace LibHac.IO.NcaUtils public byte[] Signature1; // RSA-PSS signature over header with fixed key. public byte[] Signature2; // RSA-PSS signature over header with key in NPDM. public string Magic; + public int Version; public DistributionType Distribution; // System vs gamecard. public ContentType ContentType; public byte CryptoType; // Which keyblob (field 1) @@ -33,7 +35,12 @@ namespace LibHac.IO.NcaUtils Signature1 = reader.ReadBytes(0x100); Signature2 = reader.ReadBytes(0x100); Magic = reader.ReadAscii(4); - if (Magic != "NCA3") throw new InvalidDataException("Not an NCA3 file"); + + if (!Magic.StartsWith("NCA") || Magic[3] < '0' || Magic[3] > '9') + throw new InvalidDataException("Unable to decrypt NCA header, or the file is not an NCA file."); + + Version = Magic[3] - '0'; + if (Version != 2 && Version != 3) throw new NotSupportedException($"NCA version {Version} is not supported."); reader.BaseStream.Position -= 4; SignatureData = reader.ReadBytes(0x200); diff --git a/src/hactoolnet/CliParser.cs b/src/hactoolnet/CliParser.cs index ef744c35..df3c1749 100644 --- a/src/hactoolnet/CliParser.cs +++ b/src/hactoolnet/CliParser.cs @@ -26,6 +26,7 @@ namespace hactoolnet new CliOption("section1dir", 1, (o, a) => o.SectionOutDir[1] = a[0]), new CliOption("section2dir", 1, (o, a) => o.SectionOutDir[2] = a[0]), new CliOption("section3dir", 1, (o, a) => o.SectionOutDir[3] = a[0]), + new CliOption("header", 1, (o, a) => o.HeaderOut = a[0]), new CliOption("exefs", 1, (o, a) => o.ExefsOut = a[0]), new CliOption("exefsdir", 1, (o, a) => o.ExefsOutDir = a[0]), new CliOption("romfs", 1, (o, a) => o.RomfsOut = a[0]), @@ -169,6 +170,7 @@ namespace hactoolnet sb.AppendLine(" --titlekeys Load title keys from an external file."); sb.AppendLine("NCA options:"); sb.AppendLine(" --plaintext Specify file path for saving a decrypted copy of the NCA."); + sb.AppendLine(" --header Specify Header file path."); sb.AppendLine(" --section0 Specify Section 0 file path."); sb.AppendLine(" --section1 Specify Section 1 file path."); sb.AppendLine(" --section2 Specify Section 2 file path."); diff --git a/src/hactoolnet/Options.cs b/src/hactoolnet/Options.cs index 17fdbf15..f4fc1038 100644 --- a/src/hactoolnet/Options.cs +++ b/src/hactoolnet/Options.cs @@ -17,6 +17,7 @@ namespace hactoolnet public string ConsoleKeyFile; public string[] SectionOut = new string[4]; public string[] SectionOutDir = new string[4]; + public string HeaderOut; public string ExefsOut; public string ExefsOutDir; public string RomfsOut; diff --git a/src/hactoolnet/ProcessNca.cs b/src/hactoolnet/ProcessNca.cs index 3f5e1d30..5cdf3d0b 100644 --- a/src/hactoolnet/ProcessNca.cs +++ b/src/hactoolnet/ProcessNca.cs @@ -15,6 +15,15 @@ namespace hactoolnet using (IStorage file = new LocalStorage(ctx.Options.InFile, FileAccess.Read)) { var nca = new Nca(ctx.Keyset, file, false); + + if (ctx.Options.HeaderOut != null) + { + using (var outHeader = new FileStream(ctx.Options.HeaderOut, FileMode.Create, FileAccess.ReadWrite)) + { + nca.OpenHeaderStorage().Slice(0, 0xc00).CopyToStream(outHeader); + } + } + nca.ValidateMasterHashes(); nca.ParseNpdm();