diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs
index 726ce303..f1b2805d 100644
--- a/hactoolnet/Program.cs
+++ b/hactoolnet/Program.cs
@@ -9,8 +9,8 @@ namespace hactoolnet
{
static void Main(string[] args)
{
- ReadNca();
- //ListSdfs(args);
+ //ReadNca();
+ ListSdfs(args);
}
private static void ListSdfs(string[] args)
@@ -24,16 +24,16 @@ namespace hactoolnet
ListTitles(sdfs);
//DecryptNax0(sdfs, "C0628FB07A89E9050BDA258F74868E8D");
- DecryptTitle(sdfs, 0x010025400AECE000);
+ //DecryptTitle(sdfs, 0x010023900AEE0000);
}
static void ReadNca()
{
var keyset = ExternalKeys.ReadKeyFile("keys.txt", "titlekeys.txt");
- using (var file = new FileStream("acc9da939a8e0b339aa3be3d409d9ada.nca", FileMode.Open, FileAccess.Read))
+ using (var file = new FileStream("bf8b106a2e68df3c3f1694636423585a.nca", FileMode.Open, FileAccess.Read))
{
var nca = new Nca(keyset, file, false);
- var romfs = nca.OpenSection(0);
+ var romfs = nca.OpenSection(0, false);
using (var output = new FileStream("romfs_net", FileMode.Create))
{
@@ -77,7 +77,7 @@ namespace hactoolnet
static SdFs LoadSdFs(string[] args)
{
- var keyset = ExternalKeys.ReadKeyFile(args[0]);
+ var keyset = ExternalKeys.ReadKeyFile(args[0], "titlekeys.txt");
keyset.SetSdSeed(args[1].ToBytes());
var sdfs = new SdFs(keyset, args[2]);
return sdfs;
@@ -103,6 +103,16 @@ namespace hactoolnet
$" {content.NcaId.ToHexString()}.nca {content.Type} {Util.GetBytesReadable(content.Size)}");
}
+ foreach (var nca in title.Ncas)
+ {
+ Console.WriteLine($" {nca.HasRightsId} {nca.NcaId} {nca.Header.ContentType}");
+
+ foreach (var sect in nca.Sections)
+ {
+ Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.CryptType} {sect.SuperblockHashValidity}");
+ }
+ }
+
Console.WriteLine("");
}
}
diff --git a/libhac/AesCtrStream.cs b/libhac/AesCtrStream.cs
index d4fb33ae..3938088b 100644
--- a/libhac/AesCtrStream.cs
+++ b/libhac/AesCtrStream.cs
@@ -24,6 +24,7 @@
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+using System;
using System.IO;
using System.Security.Cryptography;
using libhac.XTSSharp;
@@ -40,6 +41,7 @@ namespace libhac
///
public const int DefaultSectorSize = 16;
+ private readonly byte[] _initialCounter;
private readonly long _counterOffset;
private readonly byte[] _tempBuffer;
private readonly Aes _aes;
@@ -62,9 +64,16 @@ namespace libhac
/// Offset to start at in the input stream
/// The length of the created stream
/// Offset to add to the counter
- public AesCtrStream(Stream baseStream, byte[] key, long offset, long length, long counterOffset)
+ /// The value of the upper 64 bits of the counter
+ public AesCtrStream(Stream baseStream, byte[] key, long offset, long length, long counterOffset, byte[] ctrHi = null)
: base(baseStream, 0x10, offset)
{
+ _initialCounter = new byte[0x10];
+ if (ctrHi != null)
+ {
+ Array.Copy(ctrHi, _initialCounter, 8);
+ }
+
_counterOffset = counterOffset;
Length = length;
_tempBuffer = new byte[0x10];
@@ -80,7 +89,7 @@ namespace libhac
private CounterModeCryptoTransform CreateDecryptor()
{
- var dec = new CounterModeCryptoTransform(_aes, _aes.Key, new byte[0x10]);
+ var dec = new CounterModeCryptoTransform(_aes, _aes.Key, _initialCounter ?? new byte[0x10]);
dec.UpdateCounter(_counterOffset + Position);
return dec;
}
diff --git a/libhac/Nca.cs b/libhac/Nca.cs
index 14a65c63..36bea252 100644
--- a/libhac/Nca.cs
+++ b/libhac/Nca.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Security.Cryptography;
using libhac.XTSSharp;
namespace libhac
@@ -34,15 +35,24 @@ namespace libhac
{
DecryptKeyArea(keyset);
}
+ else
+ {
+ if (keyset.TitleKeys.TryGetValue(Header.RightsId, out var titleKey))
+ {
+ Crypto.DecryptEcb(keyset.titlekeks[CryptoType], titleKey, DecryptedKeys[2], 0x10);
+ }
+ }
for (int i = 0; i < 4; i++)
{
var section = ParseSection(i);
- if (section != null) Sections.Add(section);
+ if (section == null) continue;
+ Sections.Add(section);
+ ValidateSuperblockHash(i);
}
}
- public Stream OpenSection(int index)
+ public Stream OpenSection(int index, bool raw)
{
if (index >= Sections.Count) throw new ArgumentOutOfRangeException(nameof(index));
var sect = Sections[index];
@@ -50,18 +60,22 @@ namespace libhac
long offset = sect.Offset;
long size = sect.Size;
- switch (sect.Header.FsType)
+ if (!raw)
{
- case SectionFsType.Pfs0:
- offset = sect.Offset + sect.Pfs0.Pfs0Offset;
- size = sect.Pfs0.Pfs0Size;
- break;
- case SectionFsType.Romfs:
- offset = sect.Offset + (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].LogicalOffset;
- size = (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize;
- break;
- default:
- throw new ArgumentOutOfRangeException();
+ switch (sect.Header.FsType)
+ {
+ case SectionFsType.Pfs0:
+ offset = sect.Offset + sect.Pfs0.Pfs0Offset;
+ size = sect.Pfs0.Pfs0Size;
+ break;
+ case SectionFsType.Romfs:
+ offset = sect.Offset + (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1]
+ .LogicalOffset;
+ size = (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
}
Stream.Position = offset;
@@ -73,7 +87,7 @@ namespace libhac
case SectionCryptType.XTS:
break;
case SectionCryptType.CTR:
- return new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], offset, size, offset), false);
+ return new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], offset, size, offset, sect.Header.Ctr), false);
case SectionCryptType.BKTR:
break;
default:
@@ -128,6 +142,47 @@ namespace libhac
return sect;
}
+ private void ValidateSuperblockHash(int index)
+ {
+ if (index >= Sections.Count) throw new ArgumentOutOfRangeException(nameof(index));
+ var sect = Sections[index];
+ var stream = OpenSection(index, true);
+
+ byte[] expected = null;
+ byte[] actual;
+ long offset = 0;
+ long size = 0;
+
+ switch (sect.Type)
+ {
+ case SectionType.Invalid:
+ break;
+ case SectionType.Pfs0:
+ var pfs0 = sect.Header.Pfs0;
+ expected = pfs0.MasterHash;
+ offset = pfs0.HashTableOffset;
+ size = pfs0.HashTableSize;
+ break;
+ case SectionType.Romfs:
+ break;
+ case SectionType.Bktr:
+ break;
+ }
+
+ if (expected == null) return;
+
+ var hashTable = new byte[size];
+ stream.Position = offset;
+ stream.Read(hashTable, 0, hashTable.Length);
+
+ using (SHA256 hash = SHA256.Create())
+ {
+ actual = hash.ComputeHash(hashTable);
+ }
+
+ sect.SuperblockHashValidity = Util.ArraysEqual(expected, actual) ? Validity.Valid : Validity.Invalid;
+ }
+
public void Dispose()
{
if (!KeepOpen)
@@ -145,6 +200,7 @@ namespace libhac
public int SectionNum { get; set; }
public long Offset { get; set; }
public long Size { get; set; }
+ public Validity SuperblockHashValidity { get; set; }
public Pfs0Superblock Pfs0 { get; set; }
}
diff --git a/libhac/NcaStructs.cs b/libhac/NcaStructs.cs
index 74d1de23..eafa0666 100644
--- a/libhac/NcaStructs.cs
+++ b/libhac/NcaStructs.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Linq;
namespace libhac
{
@@ -96,7 +97,7 @@ namespace libhac
public Pfs0Superblock Pfs0;
public RomfsSuperblock Romfs;
public BktrSuperblock Bktr;
- public ulong Ctr;
+ public byte[] Ctr;
public NcaFsHeader(BinaryReader reader)
{
@@ -126,7 +127,7 @@ namespace libhac
}
}
- Ctr = reader.ReadUInt64();
+ Ctr = reader.ReadBytes(8).Reverse().ToArray();
reader.BaseStream.Position += 184;
}
}
@@ -280,4 +281,11 @@ namespace libhac
Romfs,
Bktr
}
+
+ public enum Validity
+ {
+ Unchecked,
+ Invalid,
+ Valid
+ }
}
diff --git a/libhac/SdFs.cs b/libhac/SdFs.cs
index 966a189b..a6449ac5 100644
--- a/libhac/SdFs.cs
+++ b/libhac/SdFs.cs
@@ -62,7 +62,7 @@ namespace libhac
var title = new Title();
// Meta contents always have 1 Partition FS section with 1 file in it
- Stream sect = nca.OpenSection(0);
+ Stream sect = nca.OpenSection(0, false);
var pfs0 = new Pfs0(sect);
var file = pfs0.GetFile(0);