diff --git a/LibHac/Aes128CtrStream.cs b/LibHac/Aes128CtrStream.cs
index 10402f89..073becb2 100644
--- a/LibHac/Aes128CtrStream.cs
+++ b/LibHac/Aes128CtrStream.cs
@@ -14,6 +14,15 @@ namespace LibHac
private readonly Aes128CtrTransform _decryptor;
protected readonly byte[] Counter;
+ ///
+ /// Creates a new stream
+ ///
+ /// The base stream
+ /// The decryption key
+ /// The initial counter
+ public Aes128CtrStream(Stream baseStream, byte[] key, byte[] counter)
+ : this(baseStream, key, 0, baseStream.Length, counter) { }
+
///
/// Creates a new stream
///
@@ -29,9 +38,11 @@ namespace LibHac
///
/// The base stream
/// The decryption key
+ /// Offset to start at in the input stream
+ /// The length of the created stream
/// The initial counter
- public Aes128CtrStream(Stream baseStream, byte[] key, byte[] counter)
- : base(baseStream, BlockSize)
+ public Aes128CtrStream(Stream baseStream, byte[] key, long offset, long length, byte[] counter)
+ : base(baseStream, BlockSize, 1, offset)
{
_counterOffset = 0;
@@ -44,13 +55,11 @@ namespace LibHac
}
}
- Length = baseStream.Length;
+ Length = length;
_tempBuffer = new byte[CryptChunkSize];
_decryptor = new Aes128CtrTransform(key, counter ?? new byte[0x10], CryptChunkSize);
Counter = _decryptor.Counter;
-
- baseStream.Position = 0;
}
///
diff --git a/LibHac/Aes128CtrTransform.cs b/LibHac/Aes128CtrTransform.cs
index 7ee220a1..38db3ada 100644
--- a/LibHac/Aes128CtrTransform.cs
+++ b/LibHac/Aes128CtrTransform.cs
@@ -63,11 +63,7 @@ namespace LibHac
private void IncrementCounter()
{
- for (int i = Counter.Length - 1; i >= 0; i--)
- {
- if (++Counter[i] != 0)
- break;
- }
+ Util.IncrementByteArray(Counter);
}
private void XorArrays(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputOffset, byte[] xor, int length)
diff --git a/LibHac/Package2.cs b/LibHac/Package2.cs
new file mode 100644
index 00000000..0f6a3c1f
--- /dev/null
+++ b/LibHac/Package2.cs
@@ -0,0 +1,154 @@
+using System;
+using System.IO;
+using LibHac.Streams;
+
+namespace LibHac
+{
+ public class Package2
+ {
+ private const uint Pk21Magic = 0x31324B50; // PK21
+
+ public Package2Header Header { get; }
+ public int KeyRevision { get; }
+ public byte[] Key { get; }
+ public int PackageSize { get; }
+ public int HeaderVersion { get; }
+
+ private SharedStreamSource StreamSource { get; }
+
+ public Package2(Keyset keyset, Stream stream)
+ {
+ StreamSource = new SharedStreamSource(stream);
+ SharedStream headerStream = StreamSource.CreateStream(0, 0x200);
+
+ KeyRevision = FindKeyGeneration(keyset, headerStream);
+ Key = keyset.Package2Keys[KeyRevision];
+
+ Header = new Package2Header(headerStream, Key);
+
+ PackageSize = BitConverter.ToInt32(Header.Counter, 0) ^ BitConverter.ToInt32(Header.Counter, 8) ^
+ BitConverter.ToInt32(Header.Counter, 12);
+
+ HeaderVersion = Header.Counter[4] ^ Header.Counter[6] ^ Header.Counter[7];
+
+ if (PackageSize != 0x200 + Header.SectionSizes[0] + Header.SectionSizes[1] + Header.SectionSizes[2])
+ {
+ throw new InvalidDataException("Package2 Header is corrupt!");
+ }
+ }
+
+ public Stream OpenHeaderPart1()
+ {
+ return StreamSource.CreateStream(0, 0x110);
+ }
+
+ public Stream OpenHeaderPart2()
+ {
+ SharedStream encStream = StreamSource.CreateStream(0x110, 0xF0);
+
+ var counter = new byte[0x10];
+ Array.Copy(Header.Counter, counter, 0x10);
+ Util.IncrementByteArray(counter);
+
+ return new RandomAccessSectorStream(new Aes128CtrStream(encStream, Key, counter));
+ }
+
+ public Stream OpenKernel()
+ {
+ int offset = 0x200;
+ SharedStream encStream = StreamSource.CreateStream(offset, Header.SectionSizes[0]);
+
+ return new RandomAccessSectorStream(new Aes128CtrStream(encStream, Key, Header.SectionCounters[0]));
+ }
+
+ public Stream OpenIni1()
+ {
+ int offset = 0x200 + Header.SectionSizes[0];
+ SharedStream encStream = StreamSource.CreateStream(offset, Header.SectionSizes[1]);
+
+ return new RandomAccessSectorStream(new Aes128CtrStream(encStream, Key, Header.SectionCounters[1]));
+ }
+
+ private int FindKeyGeneration(Keyset keyset, Stream stream)
+ {
+ var counter = new byte[0x10];
+ var decBuffer = new byte[0x10];
+
+ stream.Position = 0x100;
+ stream.Read(counter, 0, 0x10);
+
+ for (int i = 0; i < 0x20; i++)
+ {
+ var dec = new Aes128CtrStream(stream, keyset.Package2Keys[i], 0x100, 0x100, counter);
+ dec.Position = 0x50;
+ dec.Read(decBuffer, 0, 0x10);
+
+ if (BitConverter.ToUInt32(decBuffer, 0) == Pk21Magic)
+ {
+ stream.Position = 0;
+ return i;
+ }
+ }
+
+ throw new InvalidDataException("Failed to decrypt package2! Is the correct key present?");
+ }
+ }
+
+ public class Package2Header
+ {
+ public byte[] Signature { get; }
+ public byte[] Counter { get; }
+
+ public byte[][] SectionCounters { get; } = new byte[4][];
+ public int[] SectionSizes { get; } = new int[4];
+ public int[] SectionOffsets { get; } = new int[4];
+ public byte[][] SectionHashes { get; } = new byte[4][];
+
+ public string Magic { get; }
+ public int BaseOffset { get; }
+ public int VersionMax { get; }
+ public int VersionMin { get; }
+
+ public Package2Header(Stream stream, byte[] key)
+ {
+ var reader = new BinaryReader(stream);
+
+ Signature = reader.ReadBytes(0x100);
+ Counter = reader.ReadBytes(0x10);
+
+ var headerStream = new RandomAccessSectorStream(new Aes128CtrStream(stream, key, 0x100, 0x100, Counter));
+
+ headerStream.Position = 0x10;
+ reader = new BinaryReader(headerStream);
+
+ for (int i = 0; i < 4; i++)
+ {
+ SectionCounters[i] = reader.ReadBytes(0x10);
+ }
+
+ Magic = reader.ReadAscii(4);
+ BaseOffset = reader.ReadInt32();
+
+ reader.BaseStream.Position += 4;
+ VersionMax = reader.ReadByte();
+ VersionMin = reader.ReadByte();
+
+ reader.BaseStream.Position += 2;
+
+ for (int i = 0; i < 4; i++)
+ {
+ SectionSizes[i] = reader.ReadInt32();
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ SectionOffsets[i] = reader.ReadInt32();
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ SectionHashes[i] = reader.ReadBytes(0x20);
+ }
+ }
+ }
+}
diff --git a/LibHac/Streams/SectorStream.cs b/LibHac/Streams/SectorStream.cs
index b26adf7f..80ac740c 100644
--- a/LibHac/Streams/SectorStream.cs
+++ b/LibHac/Streams/SectorStream.cs
@@ -63,6 +63,7 @@ namespace LibHac.Streams
_offset = offset;
_keepOpen = keepOpen;
_maxBufferSize = MaxSectors * SectorSize;
+ baseStream.Position = offset;
}
public override void Flush()
diff --git a/LibHac/Util.cs b/LibHac/Util.cs
index fec22b42..3b51a33c 100644
--- a/LibHac/Util.cs
+++ b/LibHac/Util.cs
@@ -322,6 +322,15 @@ namespace LibHac
public static int AlignDown(int value, int multiple) => value - value % multiple;
public static long AlignDown(long value, long multiple) => value - value % multiple;
+ public static void IncrementByteArray(byte[] array)
+ {
+ for (int i = array.Length - 1; i >= 0; i--)
+ {
+ if (++array[i] != 0)
+ break;
+ }
+ }
+
public static void MemDump(this StringBuilder sb, string prefix, byte[] data)
{
@@ -407,5 +416,6 @@ namespace LibHac
return (hi.GetHashCode() * 397) ^ lo.GetHashCode();
}
+
}
}
diff --git a/README.md b/README.md
index a267035f..8c54efae 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ Options:
-r, --raw Keep raw data, don't unpack.
-y, --verify Verify hashes.
-k, --keyset Load keys from an external file.
- -t, --intype=type Specify input file type [nca, xci, romfs, pk11, switchfs, save, keygen]
+ -t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, switchfs, save, keygen]
--titlekeys Load title keys from an external file.
NCA options:
--section0 Specify Section 0 file path.
@@ -51,6 +51,8 @@ XCI options:
--nspout Specify file for the created NSP.
Package1 options:
--outdir Specify Package1 directory path.
+Package2 options:
+ --outdir Specify Package2 directory path.
Switch FS options:
--sdseed Set console unique seed for SD card NAX0 encryption.
--listapps List application info.
diff --git a/hactoolnet/CliParser.cs b/hactoolnet/CliParser.cs
index b92b37ba..11e62bef 100644
--- a/hactoolnet/CliParser.cs
+++ b/hactoolnet/CliParser.cs
@@ -147,7 +147,7 @@ namespace hactoolnet
sb.AppendLine(" -r, --raw Keep raw data, don\'t unpack.");
sb.AppendLine(" -y, --verify Verify hashes.");
sb.AppendLine(" -k, --keyset Load keys from an external file.");
- sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pk11, switchfs, save, keygen]");
+ sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, switchfs, save, keygen]");
sb.AppendLine(" --titlekeys Load title keys from an external file.");
sb.AppendLine("NCA options:");
sb.AppendLine(" --section0 Specify Section 0 file path.");
@@ -181,6 +181,8 @@ namespace hactoolnet
sb.AppendLine(" --nspout Specify file for the created NSP.");
sb.AppendLine("Package1 options:");
sb.AppendLine(" --outdir Specify Package1 directory path.");
+ sb.AppendLine("Package2 options:");
+ sb.AppendLine(" --outdir Specify Package2 directory path.");
sb.AppendLine("Switch FS options:");
sb.AppendLine(" --sdseed Set console unique seed for SD card NAX0 encryption.");
sb.AppendLine(" --listapps List application info.");
diff --git a/hactoolnet/Options.cs b/hactoolnet/Options.cs
index 2b051435..7e8ba384 100644
--- a/hactoolnet/Options.cs
+++ b/hactoolnet/Options.cs
@@ -47,7 +47,8 @@ namespace hactoolnet
SwitchFs,
Save,
Keygen,
- Pk11
+ Pk11,
+ Pk21
}
internal class Context
diff --git a/hactoolnet/ProcessPackage.cs b/hactoolnet/ProcessPackage.cs
index ca696472..7d15394d 100644
--- a/hactoolnet/ProcessPackage.cs
+++ b/hactoolnet/ProcessPackage.cs
@@ -1,5 +1,7 @@
using System.IO;
+using System.Text;
using LibHac;
+using static hactoolnet.Print;
namespace hactoolnet
{
@@ -28,5 +30,58 @@ namespace hactoolnet
}
}
}
+
+ public static void ProcessPk21(Context ctx)
+ {
+ using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
+ {
+ var package2 = new Package2(ctx.Keyset, file);
+
+ ctx.Logger.LogMessage(package2.Print());
+
+ string outDir = ctx.Options.OutDir;
+
+ if (outDir != null)
+ {
+ Directory.CreateDirectory(outDir);
+
+ package2.OpenKernel().WriteAllBytes(Path.Combine(outDir, "Kernel.bin"), ctx.Logger);
+ package2.OpenIni1().WriteAllBytes(Path.Combine(outDir, "INI1.bin"), ctx.Logger);
+
+ using (var decFile = new FileStream(Path.Combine(outDir, "Decrypted.bin"), FileMode.Create))
+ {
+ package2.OpenHeaderPart1().CopyTo(decFile);
+ package2.OpenHeaderPart2().CopyTo(decFile);
+ package2.OpenKernel().CopyTo(decFile);
+ package2.OpenIni1().CopyTo(decFile);
+ }
+ }
+ }
+ }
+
+ private static readonly string[] Package2SectionNames = { "Kernel", "INI1", "Empty" };
+
+ private static string Print(this Package2 package2)
+ {
+ int colLen = 36;
+ var sb = new StringBuilder();
+ sb.AppendLine();
+
+ sb.AppendLine("PK21:");
+ PrintItem(sb, colLen, "Signature:", package2.Header.Signature);
+ PrintItem(sb, colLen, "Header Version:", $"{package2.HeaderVersion:x2}");
+
+ for (int i = 0; i < 3; i++)
+ {
+ sb.AppendLine($"Section {i} ({Package2SectionNames[i]}):");
+
+ PrintItem(sb, colLen, " Hash:", package2.Header.SectionHashes[i]);
+ PrintItem(sb, colLen, " CTR:", package2.Header.SectionCounters[i]);
+ PrintItem(sb, colLen, " Load Address:", $"{package2.Header.SectionOffsets[i] + 0x80000000:x8}");
+ PrintItem(sb, colLen, " Size:", $"{package2.Header.SectionSizes[i]:x8}");
+ }
+
+ return sb.ToString();
+ }
}
}
diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs
index 6fe5f10b..0340636c 100644
--- a/hactoolnet/Program.cs
+++ b/hactoolnet/Program.cs
@@ -53,6 +53,9 @@ namespace hactoolnet
case FileType.Pk11:
ProcessPackage.ProcessPk11(ctx);
break;
+ case FileType.Pk21:
+ ProcessPackage.ProcessPk21(ctx);
+ break;
default:
throw new ArgumentOutOfRangeException();
}