From 1f62706d4c63f52276bdab3388beef9c6ee22c12 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Thu, 13 Sep 2018 17:58:40 -0500 Subject: [PATCH] Add XCI signature checks and info output --- LibHac/Nca.cs | 132 --------------------------------------- LibHac/Romfs.cs | 2 +- LibHac/Util.cs | 2 +- LibHac/Xci.cs | 65 ++++++++++++------- hactoolnet/Print.cs | 30 +++++++++ hactoolnet/ProcessNca.cs | 112 ++++++++++++++++++++++++++++++++- hactoolnet/ProcessXci.cs | 79 +++++++++++++++++++++++ 7 files changed, 264 insertions(+), 158 deletions(-) create mode 100644 hactoolnet/Print.cs diff --git a/LibHac/Nca.cs b/LibHac/Nca.cs index 3e8834ec..db312f0c 100644 --- a/LibHac/Nca.cs +++ b/LibHac/Nca.cs @@ -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; - } - } } } diff --git a/LibHac/Romfs.cs b/LibHac/Romfs.cs index 75fe4d63..1e7fbb0b 100644 --- a/LibHac/Romfs.cs +++ b/LibHac/Romfs.cs @@ -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 Directories { get; } = new List(); public List Files { get; } = new List(); diff --git a/LibHac/Util.cs b/LibHac/Util.cs index 8e1a0179..fec22b42 100644 --- a/LibHac/Util.cs +++ b/LibHac/Util.cs @@ -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; } diff --git a/LibHac/Xci.cs b/LibHac/Xci.cs index 901d9404..4f7f0f7f 100644 --- a/LibHac/Xci.cs +++ b/LibHac/Xci.cs @@ -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 Partitions { get; } = new List(); 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) { } + } } diff --git a/hactoolnet/Print.cs b/hactoolnet/Print.cs new file mode 100644 index 00000000..f9df4879 --- /dev/null +++ b/hactoolnet/Print.cs @@ -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; + } + } + } +} diff --git a/hactoolnet/ProcessNca.cs b/hactoolnet/ProcessNca.cs index a071fcfa..b01a336f 100644 --- a/hactoolnet/ProcessNca.cs +++ b/hactoolnet/ProcessNca.cs @@ -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}"); + } } } } diff --git a/hactoolnet/ProcessXci.cs b/hactoolnet/ProcessXci.cs index b2f11f8a..7824d94b 100644 --- a/hactoolnet/ProcessXci.cs +++ b/hactoolnet/ProcessXci.cs @@ -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; + } + } } }