Add XCI signature checks and info output

This commit is contained in:
Alex Barney 2018-09-13 17:58:40 -05:00
parent c321f65f2a
commit 1f62706d4c
7 changed files with 264 additions and 158 deletions

View file

@ -2,7 +2,6 @@
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using LibHac.Streams;
using LibHac.XTSSharp;
@ -430,136 +429,5 @@ namespace LibHac
break;
}
}
public static string Dump(this Nca nca)
{
int colLen = 36;
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("NCA:");
PrintItem("Magic:", nca.Header.Magic);
PrintItem("Fixed-Key Signature:", nca.Header.Signature1);
PrintItem("NPDM Signature:", nca.Header.Signature2);
PrintItem("Content Size:", $"0x{nca.Header.NcaSize:x12}");
PrintItem("TitleID:", $"{nca.Header.TitleId:X16}");
PrintItem("SDK Version:", nca.Header.SdkVersion);
PrintItem("Distribution type:", nca.Header.Distribution);
PrintItem("Content Type:", nca.Header.ContentType);
PrintItem("Master Key Revision:", $"{nca.CryptoType} ({Util.GetKeyRevisionSummary(nca.CryptoType)})");
PrintItem("Encryption Type:", $"{(nca.HasRightsId ? "Titlekey crypto" : "Standard crypto")}");
if (nca.HasRightsId)
{
PrintItem("Rights ID:", nca.Header.RightsId);
}
else
{
PrintItem("Key Area Encryption Key:", nca.Header.KaekInd);
sb.AppendLine("Key Area (Encrypted):");
for (int i = 0; i < 4; i++)
{
PrintItem($" Key {i} (Encrypted):", nca.Header.EncryptedKeys[i]);
}
sb.AppendLine("Key Area (Decrypted):");
for (int i = 0; i < 4; i++)
{
PrintItem($" Key {i} (Decrypted):", nca.DecryptedKeys[i]);
}
}
PrintSections();
return sb.ToString();
void PrintSections()
{
sb.AppendLine("Sections:");
for (int i = 0; i < 4; i++)
{
NcaSection sect = nca.Sections[i];
if (sect == null) continue;
sb.AppendLine($" Section {i}:");
PrintItem(" Offset:", $"0x{sect.Offset:x12}");
PrintItem(" Size:", $"0x{sect.Size:x12}");
PrintItem(" Partition Type:", sect.IsExefs ? "ExeFS" : sect.Type.ToString());
PrintItem(" Section CTR:", sect.Header.Ctr);
switch (sect.Type)
{
case SectionType.Pfs0:
PrintPfs0(sect);
break;
case SectionType.Romfs:
PrintRomfs(sect);
break;
case SectionType.Bktr:
break;
default:
sb.AppendLine(" Unknown/invalid superblock!");
break;
}
}
}
void PrintPfs0(NcaSection sect)
{
var sBlock = sect.Pfs0.Superblock;
PrintItem($" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", sBlock.MasterHash);
sb.AppendLine($" Hash Table{sect.Pfs0.Validity.GetValidityString()}:");
PrintItem(" Offset:", $"0x{sBlock.HashTableOffset:x12}");
PrintItem(" Size:", $"0x{sBlock.HashTableSize:x12}");
PrintItem(" Block Size:", $"0x{sBlock.BlockSize:x}");
PrintItem(" PFS0 Offset:", $"0x{sBlock.Pfs0Offset:x12}");
PrintItem(" PFS0 Size:", $"0x{sBlock.Pfs0Size:x12}");
}
void PrintRomfs(NcaSection sect)
{
var sBlock = sect.Romfs.Superblock;
var levels = sect.Romfs.IvfcLevels;
PrintItem($" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", sBlock.IvfcHeader.MasterHash);
PrintItem(" Magic:", sBlock.IvfcHeader.Magic);
PrintItem(" ID:", $"{sBlock.IvfcHeader.Id:x8}");
for (int i = 0; i < Romfs.IvfcMaxLevel; i++)
{
var level = levels[i];
sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:");
PrintItem(" Data Offset:", $"0x{level.DataOffset:x12}");
PrintItem(" Data Size:", $"0x{level.DataSize:x12}");
PrintItem(" Hash Offset:", $"0x{level.HashOffset:x12}");
PrintItem(" Hash BlockSize:", $"0x{level.HashBlockSize:x8}");
}
}
void PrintItem(string prefix, object data)
{
if (data is byte[] byteData)
{
sb.MemDump(prefix.PadRight(colLen), byteData);
}
else
{
sb.AppendLine(prefix.PadRight(colLen) + data);
}
}
}
public static string GetValidityString(this Validity validity)
{
switch (validity)
{
case Validity.Invalid: return " (FAIL)";
case Validity.Valid: return " (GOOD)";
default: return string.Empty;
}
}
}
}

View file

@ -9,7 +9,7 @@ namespace LibHac
{
public class Romfs
{
internal const int IvfcMaxLevel = 6;
public const int IvfcMaxLevel = 6;
public RomfsHeader Header { get; }
public List<RomfsDir> Directories { get; } = new List<RomfsDir>();
public List<RomfsFile> Files { get; } = new List<RomfsFile>();

View file

@ -251,7 +251,7 @@ namespace LibHac
return new string(result);
}
internal static long MediaToReal(long media)
public static long MediaToReal(long media)
{
return MediaSize * media;
}

View file

@ -1,47 +1,66 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using LibHac.Streams;
namespace LibHac
{
public class Xci
{
private const string RootPartitionName = "rootpt";
private const string UpdatePartitionName = "update";
private const string NormalPartitionName = "normal";
private const string SecurePartitionName = "secure";
private const string LogoPartitionName = "logo";
public XciHeader Header { get; }
public Pfs RootPartition { get; }
public Pfs UpdatePartition { get; }
public Pfs NormalPartition { get; }
public Pfs SecurePartition { get; }
public Pfs LogoPartition { get; }
public XciPartition RootPartition { get; }
public XciPartition UpdatePartition { get; }
public XciPartition NormalPartition { get; }
public XciPartition SecurePartition { get; }
public XciPartition LogoPartition { get; }
public List<XciPartition> Partitions { get; } = new List<XciPartition>();
public Xci(Keyset keyset, Stream stream)
{
Header = new XciHeader(keyset, stream);
var hfs0Stream = new SubStream(stream, Header.PartitionFsHeaderAddress);
RootPartition = new Pfs(hfs0Stream);
if (RootPartition.TryOpenFile(UpdatePartitionName, out var updateStream))
RootPartition = new XciPartition(hfs0Stream)
{
UpdatePartition = new Pfs(updateStream);
Name = RootPartitionName,
Offset = Header.PartitionFsHeaderAddress
};
Partitions.Add(RootPartition);
foreach (PfsFileEntry file in RootPartition.Files)
{
Stream partitionStream = RootPartition.OpenFile(file);
var partition = new XciPartition(partitionStream)
{
Name = file.Name,
Offset = Header.PartitionFsHeaderAddress + RootPartition.HeaderSize + file.Offset
};
Partitions.Add(partition);
}
if (RootPartition.TryOpenFile(NormalPartitionName, out var normalStream))
{
NormalPartition = new Pfs(normalStream);
}
if (RootPartition.TryOpenFile(SecurePartitionName, out var secureStream))
{
SecurePartition = new Pfs(secureStream);
}
if (RootPartition.TryOpenFile(LogoPartitionName, out var logoStream))
{
LogoPartition = new Pfs(logoStream);
}
UpdatePartition = Partitions.FirstOrDefault(x => x.Name == UpdatePartitionName);
NormalPartition = Partitions.FirstOrDefault(x => x.Name == NormalPartitionName);
SecurePartition = Partitions.FirstOrDefault(x => x.Name == SecurePartitionName);
LogoPartition = Partitions.FirstOrDefault(x => x.Name == LogoPartitionName);
}
}
public class XciPartition : Pfs
{
public string Name { get; internal set; }
public long Offset { get; internal set; }
public XciPartition(Stream stream) : base(stream) { }
}
}

30
hactoolnet/Print.cs Normal file
View file

@ -0,0 +1,30 @@
using System.Text;
using LibHac;
namespace hactoolnet
{
internal static class Print
{
public static void PrintItem(StringBuilder sb, int colLen, string prefix, object data)
{
if (data is byte[] byteData)
{
sb.MemDump(prefix.PadRight(colLen), byteData);
}
else
{
sb.AppendLine(prefix.PadRight(colLen) + data);
}
}
public static string GetValidityString(this Validity validity)
{
switch (validity)
{
case Validity.Invalid: return " (FAIL)";
case Validity.Valid: return " (GOOD)";
default: return string.Empty;
}
}
}
}

View file

@ -1,6 +1,8 @@
using System.IO;
using System.Linq;
using System.Text;
using LibHac;
using static hactoolnet.Print;
namespace hactoolnet
{
@ -96,7 +98,115 @@ namespace hactoolnet
}
}
ctx.Logger.LogMessage(nca.Dump());
ctx.Logger.LogMessage(nca.Print());
}
}
private static string Print(this Nca nca)
{
int colLen = 36;
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("NCA:");
PrintItem(sb, colLen, "Magic:", nca.Header.Magic);
PrintItem(sb, colLen, "Fixed-Key Signature:", nca.Header.Signature1);
PrintItem(sb, colLen, "NPDM Signature:", nca.Header.Signature2);
PrintItem(sb, colLen, "Content Size:", $"0x{nca.Header.NcaSize:x12}");
PrintItem(sb, colLen, "TitleID:", $"{nca.Header.TitleId:X16}");
PrintItem(sb, colLen, "SDK Version:", nca.Header.SdkVersion);
PrintItem(sb, colLen, "Distribution type:", nca.Header.Distribution);
PrintItem(sb, colLen, "Content Type:", nca.Header.ContentType);
PrintItem(sb, colLen, "Master Key Revision:", $"{nca.CryptoType} ({Util.GetKeyRevisionSummary(nca.CryptoType)})");
PrintItem(sb, colLen, "Encryption Type:", $"{(nca.HasRightsId ? "Titlekey crypto" : "Standard crypto")}");
if (nca.HasRightsId)
{
PrintItem(sb, colLen, "Rights ID:", nca.Header.RightsId);
}
else
{
PrintItem(sb, colLen, "Key Area Encryption Key:", nca.Header.KaekInd);
sb.AppendLine("Key Area (Encrypted):");
for (int i = 0; i < 4; i++)
{
PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.EncryptedKeys[i]);
}
sb.AppendLine("Key Area (Decrypted):");
for (int i = 0; i < 4; i++)
{
PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.DecryptedKeys[i]);
}
}
PrintSections();
return sb.ToString();
void PrintSections()
{
sb.AppendLine("Sections:");
for (int i = 0; i < 4; i++)
{
NcaSection sect = nca.Sections[i];
if (sect == null) continue;
sb.AppendLine($" Section {i}:");
PrintItem(sb, colLen, " Offset:", $"0x{sect.Offset:x12}");
PrintItem(sb, colLen, " Size:", $"0x{sect.Size:x12}");
PrintItem(sb, colLen, " Partition Type:", sect.IsExefs ? "ExeFS" : sect.Type.ToString());
PrintItem(sb, colLen, " Section CTR:", sect.Header.Ctr);
switch (sect.Type)
{
case SectionType.Pfs0:
PrintPfs0(sect);
break;
case SectionType.Romfs:
PrintRomfs(sect);
break;
case SectionType.Bktr:
break;
default:
sb.AppendLine(" Unknown/invalid superblock!");
break;
}
}
}
void PrintPfs0(NcaSection sect)
{
var sBlock = sect.Pfs0.Superblock;
PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", sBlock.MasterHash);
sb.AppendLine($" Hash Table{sect.Pfs0.Validity.GetValidityString()}:");
PrintItem(sb, colLen, " Offset:", $"0x{sBlock.HashTableOffset:x12}");
PrintItem(sb, colLen, " Size:", $"0x{sBlock.HashTableSize:x12}");
PrintItem(sb, colLen, " Block Size:", $"0x{sBlock.BlockSize:x}");
PrintItem(sb, colLen, " PFS0 Offset:", $"0x{sBlock.Pfs0Offset:x12}");
PrintItem(sb, colLen, " PFS0 Size:", $"0x{sBlock.Pfs0Size:x12}");
}
void PrintRomfs(NcaSection sect)
{
var sBlock = sect.Romfs.Superblock;
var levels = sect.Romfs.IvfcLevels;
PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", sBlock.IvfcHeader.MasterHash);
PrintItem(sb, colLen, " Magic:", sBlock.IvfcHeader.Magic);
PrintItem(sb, colLen, " ID:", $"{sBlock.IvfcHeader.Id:x8}");
for (int i = 0; i < Romfs.IvfcMaxLevel; i++)
{
var level = levels[i];
sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:");
PrintItem(sb, colLen, " Data Offset:", $"0x{level.DataOffset:x12}");
PrintItem(sb, colLen, " Data Size:", $"0x{level.DataSize:x12}");
PrintItem(sb, colLen, " Hash Offset:", $"0x{level.HashOffset:x12}");
PrintItem(sb, colLen, " Hash BlockSize:", $"0x{level.HashBlockSize:x8}");
}
}
}
}

View file

@ -1,6 +1,8 @@
using System.IO;
using System.Linq;
using System.Text;
using LibHac;
using static hactoolnet.Print;
namespace hactoolnet
{
@ -12,6 +14,8 @@ namespace hactoolnet
{
var xci = new Xci(ctx.Keyset, file);
ctx.Logger.LogMessage(xci.Print());
if (ctx.Options.RootDir != null)
{
xci.RootPartition?.Extract(ctx.Options.RootDir, ctx.Logger);
@ -139,5 +143,80 @@ namespace hactoolnet
return mainNca;
}
private static string Print(this Xci xci)
{
const int colLen = 36;
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("XCI:");
PrintItem(sb, colLen, "Magic:", xci.Header.Magic);
PrintItem(sb, colLen, $"Header Signature:{xci.Header.SignatureValidity.GetValidityString()}", xci.Header.Signature);
PrintItem(sb, colLen, "Cartridge Type:", GetCartridgeType(xci.Header.RomSize));
PrintItem(sb, colLen, "Cartridge Size:", $"0x{Util.MediaToReal(xci.Header.ValidDataEndPage + 1):x12}");
PrintItem(sb, colLen, "Header IV:", xci.Header.AesCbcIv);
foreach (XciPartition partition in xci.Partitions.OrderBy(x => x.Offset))
{
PrintPartition(sb, colLen, partition);
}
return sb.ToString();
}
private static void PrintPartition(StringBuilder sb, int colLen, XciPartition partition)
{
const int fileNameLen = 57;
sb.AppendLine($"{GetDisplayName(partition.Name)} Partition:");
PrintItem(sb, colLen, " Magic:", partition.Header.Magic);
PrintItem(sb, colLen, " Offset:", $"{partition.Offset:x12}");
PrintItem(sb, colLen, " Number of files:", partition.Files.Length);
if (partition.Files.Length > 0 && partition.Files.Length < 100)
{
for (int i = 0; i < partition.Files.Length; i++)
{
PfsFileEntry file = partition.Files[i];
string label = i == 0 ? " Files:" : "";
string offsets = $"{file.Offset:x12}-{file.Offset + file.Size:x12}";
string data = $"{partition.Name}:/{file.Name}".PadRight(fileNameLen) + offsets;
PrintItem(sb, colLen, label, data);
}
}
}
private static string GetDisplayName(string name)
{
switch (name)
{
case "rootpt": return "Root";
case "update": return "Update";
case "normal": return "Normal";
case "secure": return "Secure";
case "logo": return "Logo";
default: return name;
}
}
private static string GetCartridgeType(RomSize size)
{
switch (size)
{
case RomSize.Size1Gb: return "1GB";
case RomSize.Size2Gb: return "2GB";
case RomSize.Size4Gb: return "4GB";
case RomSize.Size8Gb: return "8GB";
case RomSize.Size16Gb: return "16GB";
case RomSize.Size32Gb: return "32GB";
default: return string.Empty;
}
}
}
}