2018-09-13 03:28:50 +02:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
2018-09-14 00:58:40 +02:00
|
|
|
|
using System.Text;
|
2018-09-13 03:28:50 +02:00
|
|
|
|
using LibHac;
|
2018-09-14 00:58:40 +02:00
|
|
|
|
using static hactoolnet.Print;
|
2018-09-13 03:28:50 +02:00
|
|
|
|
|
|
|
|
|
namespace hactoolnet
|
|
|
|
|
{
|
|
|
|
|
internal static class ProcessNca
|
|
|
|
|
{
|
|
|
|
|
public static void Process(Context ctx)
|
|
|
|
|
{
|
|
|
|
|
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
|
|
|
|
|
{
|
|
|
|
|
var nca = new Nca(ctx.Keyset, file, false);
|
2018-10-09 04:04:39 +02:00
|
|
|
|
nca.ValidateMasterHashes();
|
2018-09-13 03:28:50 +02:00
|
|
|
|
|
|
|
|
|
if (ctx.Options.BaseNca != null)
|
|
|
|
|
{
|
|
|
|
|
var baseFile = new FileStream(ctx.Options.BaseNca, FileMode.Open, FileAccess.Read);
|
|
|
|
|
var baseNca = new Nca(ctx.Keyset, baseFile, false);
|
|
|
|
|
nca.SetBaseNca(baseNca);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
|
|
|
{
|
|
|
|
|
if (ctx.Options.SectionOut[i] != null)
|
|
|
|
|
{
|
2018-10-09 22:33:56 +02:00
|
|
|
|
nca.ExportSection(i, ctx.Options.SectionOut[i], ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
|
2018-09-13 03:28:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.SectionOutDir[i] != null)
|
|
|
|
|
{
|
2018-10-09 22:33:56 +02:00
|
|
|
|
nca.ExtractSection(i, ctx.Options.SectionOutDir[i], ctx.Options.IntegrityLevel, ctx.Logger);
|
2018-09-13 03:28:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.Validate && nca.Sections[i] != null)
|
|
|
|
|
{
|
|
|
|
|
nca.VerifySection(i, ctx.Logger);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.ListRomFs && nca.Sections[1] != null)
|
|
|
|
|
{
|
2018-10-09 22:33:56 +02:00
|
|
|
|
var romfs = new Romfs(nca.OpenSection(1, false, ctx.Options.IntegrityLevel));
|
2018-09-13 03:28:50 +02:00
|
|
|
|
|
2018-10-03 00:25:58 +02:00
|
|
|
|
foreach (RomfsFile romfsFile in romfs.Files)
|
2018-09-13 03:28:50 +02:00
|
|
|
|
{
|
|
|
|
|
ctx.Logger.LogMessage(romfsFile.FullPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null)
|
|
|
|
|
{
|
|
|
|
|
NcaSection section = nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs || x?.Type == SectionType.Bktr);
|
|
|
|
|
|
|
|
|
|
if (section == null)
|
|
|
|
|
{
|
|
|
|
|
ctx.Logger.LogMessage("NCA has no RomFS section");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (section.Type == SectionType.Bktr && ctx.Options.BaseNca == null)
|
|
|
|
|
{
|
|
|
|
|
ctx.Logger.LogMessage("Cannot save BKTR section without base RomFS");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.RomfsOut != null)
|
|
|
|
|
{
|
2018-10-09 22:33:56 +02:00
|
|
|
|
nca.ExportSection(section.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
|
2018-09-13 03:28:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.RomfsOutDir != null)
|
|
|
|
|
{
|
2018-10-09 22:33:56 +02:00
|
|
|
|
var romfs = new Romfs(nca.OpenSection(section.SectionNum, false, ctx.Options.IntegrityLevel));
|
2018-09-13 03:28:50 +02:00
|
|
|
|
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null)
|
|
|
|
|
{
|
2018-10-13 04:28:05 +02:00
|
|
|
|
if (nca.Header.ContentType != ContentType.Program)
|
|
|
|
|
{
|
|
|
|
|
ctx.Logger.LogMessage("NCA's content type is not \"Program\"");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NcaSection section = nca.Sections[(int)ProgramPartitionType.Code];
|
2018-09-13 03:28:50 +02:00
|
|
|
|
|
|
|
|
|
if (section == null)
|
|
|
|
|
{
|
|
|
|
|
ctx.Logger.LogMessage("Could not find an ExeFS section");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.ExefsOut != null)
|
|
|
|
|
{
|
2018-10-09 22:33:56 +02:00
|
|
|
|
nca.ExportSection(section.SectionNum, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
|
2018-09-13 03:28:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.ExefsOutDir != null)
|
|
|
|
|
{
|
2018-10-09 22:33:56 +02:00
|
|
|
|
nca.ExtractSection(section.SectionNum, ctx.Options.ExefsOutDir, ctx.Options.IntegrityLevel, ctx.Logger);
|
2018-09-13 03:28:50 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 00:58:40 +02:00
|
|
|
|
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;
|
|
|
|
|
|
2018-10-13 04:28:05 +02:00
|
|
|
|
bool isExefs = nca.Header.ContentType == ContentType.Program && i == (int)ProgramPartitionType.Code;
|
|
|
|
|
|
2018-09-14 00:58:40 +02:00
|
|
|
|
sb.AppendLine($" Section {i}:");
|
|
|
|
|
PrintItem(sb, colLen, " Offset:", $"0x{sect.Offset:x12}");
|
|
|
|
|
PrintItem(sb, colLen, " Size:", $"0x{sect.Size:x12}");
|
2018-10-13 04:28:05 +02:00
|
|
|
|
PrintItem(sb, colLen, " Partition Type:", isExefs ? "ExeFS" : sect.Type.ToString());
|
2018-09-14 00:58:40 +02:00
|
|
|
|
PrintItem(sb, colLen, " Section CTR:", sect.Header.Ctr);
|
|
|
|
|
|
2018-10-13 04:06:21 +02:00
|
|
|
|
switch (sect.Header.HashType)
|
2018-09-14 00:58:40 +02:00
|
|
|
|
{
|
2018-10-13 04:06:21 +02:00
|
|
|
|
case NcaHashType.Sha256:
|
|
|
|
|
PrintSha256Hash(sect);
|
2018-09-14 00:58:40 +02:00
|
|
|
|
break;
|
2018-10-13 04:06:21 +02:00
|
|
|
|
case NcaHashType.Ivfc:
|
2018-10-19 00:54:24 +02:00
|
|
|
|
PrintIvfcHash(sb, colLen, 8, sect.Header.IvfcInfo, IntegrityStreamType.RomFs);
|
2018-09-14 00:58:40 +02:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
sb.AppendLine(" Unknown/invalid superblock!");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-13 04:06:21 +02:00
|
|
|
|
void PrintSha256Hash(NcaSection sect)
|
2018-09-14 00:58:40 +02:00
|
|
|
|
{
|
2018-10-08 03:29:27 +02:00
|
|
|
|
Sha256Info hashInfo = sect.Header.Sha256Info;
|
|
|
|
|
|
2018-10-10 01:10:44 +02:00
|
|
|
|
PrintItem(sb, colLen, $" Master Hash{sect.MasterHashValidity.GetValidityString()}:", hashInfo.MasterHash);
|
|
|
|
|
sb.AppendLine($" Hash Table{sect.Header.Sha256Info.HashValidity.GetValidityString()}:");
|
2018-10-08 03:29:27 +02:00
|
|
|
|
|
|
|
|
|
PrintItem(sb, colLen, " Offset:", $"0x{hashInfo.HashTableOffset:x12}");
|
|
|
|
|
PrintItem(sb, colLen, " Size:", $"0x{hashInfo.HashTableSize:x12}");
|
|
|
|
|
PrintItem(sb, colLen, " Block Size:", $"0x{hashInfo.BlockSize:x}");
|
|
|
|
|
PrintItem(sb, colLen, " PFS0 Offset:", $"0x{hashInfo.DataOffset:x12}");
|
|
|
|
|
PrintItem(sb, colLen, " PFS0 Size:", $"0x{hashInfo.DataSize:x12}");
|
2018-09-14 00:58:40 +02:00
|
|
|
|
}
|
2018-09-13 03:28:50 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|