mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add XCI signature checks and info output
This commit is contained in:
parent
c321f65f2a
commit
1f62706d4c
7 changed files with 264 additions and 158 deletions
132
LibHac/Nca.cs
132
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
30
hactoolnet/Print.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue