Read NCA header

This commit is contained in:
Alex Barney 2018-06-21 16:03:58 -05:00
parent 8054d38f5a
commit e3d8e60b0e
6 changed files with 327 additions and 47 deletions

View file

@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using libhac; using libhac;
namespace hactoolnet namespace hactoolnet
@ -22,7 +23,7 @@ namespace hactoolnet
using (var output = new FileStream(args[4], FileMode.Create)) using (var output = new FileStream(args[4], FileMode.Create))
using (var progress = new ProgressBar()) using (var progress = new ProgressBar())
{ {
progress.LogMessage($"Title ID: {nca.TitleId:X8}"); progress.LogMessage($"Title ID: {nca.Header.TitleId:X16}");
progress.LogMessage($"Writing {args[4]}"); progress.LogMessage($"Writing {args[4]}");
nax0.Stream.CopyStream(output, nax0.Stream.Length, progress); nax0.Stream.CopyStream(output, nax0.Stream.Length, progress);
} }
@ -33,11 +34,11 @@ namespace hactoolnet
var keyset = ExternalKeys.ReadKeyFile(args[0]); var keyset = ExternalKeys.ReadKeyFile(args[0]);
keyset.SetSdSeed(args[1].ToBytes()); keyset.SetSdSeed(args[1].ToBytes());
var sdfs = new SdFs(keyset, args[2]); var sdfs = new SdFs(keyset, args[2]);
var ncas = sdfs.ReadAllNca(); var ncas = sdfs.ReadAllNca().ToArray();
foreach (var nca in ncas) foreach (var nca in ncas)
{ {
Console.WriteLine($"{nca.TitleId:X8} {nca.ContentType} {nca.Name}"); Console.WriteLine($"{nca.Header.TitleId:X16} {nca.Header.ContentType} {nca.Name}");
} }
} }
} }

View file

@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -31,6 +31,7 @@ namespace libhac
public byte[] header_kek_source { get; set; } = new byte[0x10]; public byte[] header_kek_source { get; set; } = new byte[0x10];
public byte[] sd_card_kek_source { get; set; } = new byte[0x10]; public byte[] sd_card_kek_source { get; set; } = new byte[0x10];
public byte[][] sd_card_key_sources { get; set; } = Util.CreateJaggedArray<byte[][]>(2, 0x20); public byte[][] sd_card_key_sources { get; set; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
public byte[][] sd_card_key_sources_specific { get; set; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
public byte[] encrypted_header_key { get; set; } = new byte[0x20]; public byte[] encrypted_header_key { get; set; } = new byte[0x20];
public byte[] header_key { get; set; } = new byte[0x20]; public byte[] header_key { get; set; } = new byte[0x20];
public byte[][] titlekeks { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10); public byte[][] titlekeks { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
@ -42,13 +43,14 @@ namespace libhac
public void SetSdSeed(byte[] sdseed) public void SetSdSeed(byte[] sdseed)
{ {
foreach (byte[] key in sd_card_key_sources) for (int k = 0; k < sd_card_key_sources.Length; k++)
{ {
for (int i = 0; i < 0x20; i++) for (int i = 0; i < 0x20; i++)
{ {
key[i] ^= sdseed[i & 0xF]; sd_card_key_sources_specific[k][i] = (byte)(sd_card_key_sources[k][i] ^ sdseed[i & 0xF]);
} }
} }
DeriveKeys(); DeriveKeys();
} }
@ -65,9 +67,9 @@ namespace libhac
var sdKek = new byte[0x10]; var sdKek = new byte[0x10];
Crypto.GenerateKek(sdKek, sd_card_kek_source, master_keys[0], aes_kek_generation_source, aes_key_generation_source); Crypto.GenerateKek(sdKek, sd_card_kek_source, master_keys[0], aes_kek_generation_source, aes_key_generation_source);
for (int k = 0; k < sd_card_key_sources.Length; k++) for (int k = 0; k < sd_card_key_sources_specific.Length; k++)
{ {
Crypto.DecryptEcb(sdKek, sd_card_key_sources[k], sd_card_keys[k], 0x20); Crypto.DecryptEcb(sdKek, sd_card_key_sources_specific[k], sd_card_keys[k], 0x20);
} }
} }
} }
@ -76,7 +78,7 @@ namespace libhac
{ {
private static readonly Dictionary<string, KeyValue> KeyDict = CreateKeyDict(); private static readonly Dictionary<string, KeyValue> KeyDict = CreateKeyDict();
public static Keyset ReadKeyFile(string filename) public static Keyset ReadKeyFile(string filename, IProgressReport progress = null)
{ {
var keyset = new Keyset(); var keyset = new Keyset();
using (var reader = new StreamReader(new FileStream(filename, FileMode.Open))) using (var reader = new StreamReader(new FileStream(filename, FileMode.Open)))
@ -92,14 +94,14 @@ namespace libhac
if (!KeyDict.TryGetValue(key, out var kv)) if (!KeyDict.TryGetValue(key, out var kv))
{ {
Console.WriteLine($"Failed to match key {key}"); progress?.LogMessage($"Failed to match key {key}");
continue; continue;
} }
var value = valueStr.ToBytes(); var value = valueStr.ToBytes();
if (value.Length != kv.Size) if (value.Length != kv.Size)
{ {
Console.WriteLine($"Key {key} had incorrect size {value.Length}. (Expected {kv.Size})"); progress?.LogMessage($"Key {key} had incorrect size {value.Length}. (Expected {kv.Size})");
continue; continue;
} }

View file

@ -1,54 +1,325 @@
using System.IO; using System;
using System.IO;
using libhac.XTSSharp; using libhac.XTSSharp;
namespace libhac namespace libhac
{ {
public class Nca public class Nca
{ {
public byte[] Signature1 { get; set; } // RSA-PSS signature over header with fixed key. public NcaHeader Header { get; private set; }
public byte[] Signature2 { get; set; } // RSA-PSS signature over header with key in NPDM.
public string Magic { get; set; }
public byte Distribution { get; set; } // System vs gamecard.
public byte ContentType { get; set; }
public byte CryptoType { get; set; } // Which keyblob (field 1)
public byte KaekInd { get; set; } // Which kaek index?
public ulong NcaSize { get; set; } // Entire archive size.
public ulong TitleId { get; set; }
public uint SdkVersion { get; set; } // What SDK was this built with?
public byte CryptoType2 { get; set; } // Which keyblob (field 2)
public byte[] RightsId { get; set; }
public string Name { get; set; } public string Name { get; set; }
public bool HasRightsId { get; private set; }
public int CryptoType { get; private set; }
public byte[][] DecryptedKeys { get; } = Util.CreateJaggedArray<byte[][]>(4, 0x10);
public Nca(Keyset keyset, Stream stream) public Nca(Keyset keyset, Stream stream)
{ {
ReadHeader(keyset, stream); ReadHeader(keyset, stream);
CryptoType = Math.Max(Header.CryptoType, Header.CryptoType2);
if (CryptoType > 0) CryptoType--;
HasRightsId = !Header.RightsId.IsEmpty();
if (!HasRightsId)
{
DecryptKeyArea(keyset);
}
} }
private void ReadHeader(Keyset keyset, Stream stream) private void ReadHeader(Keyset keyset, Stream stream)
{ {
stream.Position = 0; stream.Position = 0;
var xts = XtsAes128.Create(keyset.header_key); var xts = XtsAes128.Create(keyset.header_key);
var header = new RandomAccessSectorStream(new XtsSectorStream(stream, xts, 0x200)); var headerDec = new RandomAccessSectorStream(new XtsSectorStream(stream, xts, 0x200));
var reader = new BinaryReader(header); var reader = new BinaryReader(headerDec);
Signature1 = reader.ReadBytes(0x100); Header = NcaHeader.Read(reader);
Signature2 = reader.ReadBytes(0x100);
headerDec.Close();
}
private void DecryptKeyArea(Keyset keyset)
{
for (int i = 0; i < 4; i++)
{
Crypto.DecryptEcb(keyset.key_area_keys[CryptoType][Header.KaekInd], Header.EncryptedKeys[i],
DecryptedKeys[i], 0x10);
}
}
}
public class NcaHeader
{
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 byte Distribution; // System vs gamecard.
public ContentType ContentType;
public byte CryptoType; // Which keyblob (field 1)
public byte KaekInd; // Which kaek index?
public ulong NcaSize; // Entire archive size.
public ulong TitleId;
public uint SdkVersion; // What SDK was this built with?
public byte CryptoType2; // Which keyblob (field 2)
public byte[] RightsId;
public string Name;
public NcaSectionEntry[] SectionEntries = new NcaSectionEntry[4];
public byte[][] SectionHashes = new byte[4][];
public byte[][] EncryptedKeys = new byte[4][];
public NcaFsHeader[] FsHeaders = new NcaFsHeader[4];
public static NcaHeader Read(BinaryReader reader)
{
var head = new NcaHeader();
head.Signature1 = reader.ReadBytes(0x100);
head.Signature2 = reader.ReadBytes(0x100);
head.Magic = reader.ReadAscii(4);
head.Distribution = reader.ReadByte();
head.ContentType = (ContentType)reader.ReadByte();
head.CryptoType = reader.ReadByte();
head.KaekInd = reader.ReadByte();
head.NcaSize = reader.ReadUInt64();
head.TitleId = reader.ReadUInt64();
reader.BaseStream.Position += 4;
head.SdkVersion = reader.ReadUInt32();
head.CryptoType2 = reader.ReadByte();
reader.BaseStream.Position += 0xF;
head.RightsId = reader.ReadBytes(0x10);
for (int i = 0; i < 4; i++)
{
head.SectionEntries[i] = new NcaSectionEntry(reader);
}
for (int i = 0; i < 4; i++)
{
head.SectionHashes[i] = reader.ReadBytes(0x20);
}
for (int i = 0; i < 4; i++)
{
head.EncryptedKeys[i] = reader.ReadBytes(0x10);
}
reader.BaseStream.Position += 0xC0;
for (int i = 0; i < 4; i++)
{
head.FsHeaders[i] = new NcaFsHeader(reader);
}
return head;
}
}
public class NcaSectionEntry
{
public uint MediaStartOffset;
public uint MediaEndOffset;
public NcaSectionEntry(BinaryReader reader)
{
MediaStartOffset = reader.ReadUInt32();
MediaEndOffset = reader.ReadUInt32();
reader.BaseStream.Position += 8;
}
}
public class NcaFsHeader
{
public byte Field0;
public byte Field1;
public SectionPartitionType PartitionType;
public SectionFsType FsType;
public SectionCryptType CryptType;
public SectionType Type;
public Pfs0Superblock Pfs0;
public RomfsSuperblock Romfs;
public BktrSuperblock Bktr;
public NcaFsHeader(BinaryReader reader)
{
Field0 = reader.ReadByte();
Field1 = reader.ReadByte();
PartitionType = (SectionPartitionType)reader.ReadByte();
FsType = (SectionFsType)reader.ReadByte();
CryptType = (SectionCryptType)reader.ReadByte();
reader.BaseStream.Position += 3;
if (PartitionType == SectionPartitionType.Pfs0 && FsType == SectionFsType.Pfs0)
{
Type = SectionType.Pfs0;
Pfs0 = new Pfs0Superblock(reader);
}
else if (PartitionType == SectionPartitionType.Romfs && FsType == SectionFsType.Romfs)
{
if (CryptType == SectionCryptType.BKTR)
{
Type = SectionType.Bktr;
Bktr = new BktrSuperblock(reader);
}
else
{
Type = SectionType.Romfs;
Romfs = new RomfsSuperblock(reader);
}
}
}
}
public class Pfs0Superblock
{
public byte[] MasterHash; /* SHA-256 hash of the hash table. */
public uint BlockSize; /* In bytes. */
public uint Always2;
public ulong HashTableOffset; /* Normally zero. */
public ulong HashTableSize;
public ulong Pfs0Offset;
public ulong Pfs0Size;
public Pfs0Superblock(BinaryReader reader)
{
MasterHash = reader.ReadBytes(0x20);
BlockSize = reader.ReadUInt32();
Always2 = reader.ReadUInt32();
HashTableOffset = reader.ReadUInt64();
HashTableSize = reader.ReadUInt64();
Pfs0Offset = reader.ReadUInt64();
Pfs0Size = reader.ReadUInt64();
reader.BaseStream.Position += 0xF0;
}
}
public class RomfsSuperblock
{
public IvfcHeader IvfcHeader;
public RomfsSuperblock(BinaryReader reader)
{
IvfcHeader = new IvfcHeader(reader);
reader.BaseStream.Position += 0x58;
}
}
public class BktrSuperblock
{
public IvfcHeader IvfcHeader;
public BktrHeader RelocationHeader;
public BktrHeader SubsectionHeader;
public BktrSuperblock(BinaryReader reader)
{
IvfcHeader = new IvfcHeader(reader);
reader.BaseStream.Position += 0x18;
RelocationHeader = new BktrHeader(reader);
SubsectionHeader = new BktrHeader(reader);
}
}
public class IvfcHeader
{
public string Magic;
public uint Id;
public uint MasterHashSize;
public uint NumLevels;
public IvfcLevelHeader[] LevelHeaders = new IvfcLevelHeader[6];
public byte[] MasterHash;
public IvfcHeader(BinaryReader reader)
{
Magic = reader.ReadAscii(4); Magic = reader.ReadAscii(4);
if (Magic != "NCA3") throw new InvalidDataException("Not an NCA3 file"); Id = reader.ReadUInt32();
Distribution = reader.ReadByte(); MasterHashSize = reader.ReadUInt32();
ContentType = reader.ReadByte(); NumLevels = reader.ReadUInt32();
CryptoType = reader.ReadByte();
KaekInd = reader.ReadByte();
NcaSize = reader.ReadUInt64();
TitleId = reader.ReadUInt64();
header.Position += 4;
SdkVersion = reader.ReadUInt32(); for (int i = 0; i < LevelHeaders.Length; i++)
CryptoType2 = reader.ReadByte(); {
header.Position += 0xF; LevelHeaders[i] = new IvfcLevelHeader(reader);
}
RightsId = reader.ReadBytes(0x10); reader.BaseStream.Position += 0x20;
header.Close(); MasterHash = reader.ReadBytes(0x20);
} }
} }
public class IvfcLevelHeader
{
public ulong LogicalOffset;
public ulong HashDataSize;
public uint BlockSize;
public uint Reserved;
public IvfcLevelHeader(BinaryReader reader)
{
LogicalOffset = reader.ReadUInt64();
HashDataSize = reader.ReadUInt64();
BlockSize = reader.ReadUInt32();
Reserved = reader.ReadUInt32();
}
}
public class BktrHeader
{
public ulong Offset;
public ulong Size;
public uint Magic;
public uint Field14;
public uint NumEntries;
public uint Field1C;
public BktrHeader(BinaryReader reader)
{
Offset = reader.ReadUInt64();
Size = reader.ReadUInt64();
Magic = reader.ReadUInt32();
Field14 = reader.ReadUInt32();
NumEntries = reader.ReadUInt32();
Field1C = reader.ReadUInt32();
}
}
public enum ContentType
{
Program,
Meta,
Control,
Manual,
Data,
Unknown
}
public enum SectionCryptType
{
None = 1,
XTS,
CTR,
BKTR
}
public enum SectionFsType
{
Pfs0 = 2,
Romfs
}
public enum SectionPartitionType
{
Romfs,
Pfs0
}
public enum SectionType
{
Invalid,
Pfs0,
Romfs,
Bktr
}
} }

View file

@ -23,19 +23,17 @@ namespace libhac
Files = Directory.GetFiles(ContentsDir, "00", SearchOption.AllDirectories).Select(Path.GetDirectoryName).ToArray(); Files = Directory.GetFiles(ContentsDir, "00", SearchOption.AllDirectories).Select(Path.GetDirectoryName).ToArray();
} }
public List<Nca> ReadAllNca() public IEnumerable<Nca> ReadAllNca()
{ {
List<Nca> ncas = new List<Nca>();
foreach (var file in Files) foreach (var file in Files)
{ {
var sdPath = "/" + Util.GetRelativePath(file, ContentsDir).Replace('\\', '/'); var sdPath = "/" + Util.GetRelativePath(file, ContentsDir).Replace('\\', '/');
var nax0 = new Nax0(Keyset, file, sdPath); var nax0 = new Nax0(Keyset, file, sdPath);
var nca = new Nca(Keyset, nax0.Stream); var nca = new Nca(Keyset, nax0.Stream);
ncas.Add(nca);
nca.Name = Path.GetFileName(file); nca.Name = Path.GetFileName(file);
yield return nca;
} }
return ncas;
} }
} }
} }

View file

@ -6,6 +6,8 @@ namespace libhac
{ {
public static class Util public static class Util
{ {
private const int MediaSize = 0x200;
public static T CreateJaggedArray<T>(params int[] lengths) public static T CreateJaggedArray<T>(params int[] lengths)
{ {
return (T)InitializeJaggedArray(typeof(T).GetElementType(), 0, lengths); return (T)InitializeJaggedArray(typeof(T).GetElementType(), 0, lengths);
@ -49,7 +51,7 @@ namespace libhac
for (int i = 0; i < array.Length; i++) for (int i = 0; i < array.Length; i++)
{ {
if (i != 0) if (array[i] != 0)
{ {
return false; return false;
} }
@ -159,5 +161,10 @@ namespace libhac
} }
return result; return result;
} }
internal static ulong MediaToReal(ulong media)
{
return MediaSize * media;
}
} }
} }