mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Mimic hactool output. Verify hashes
This commit is contained in:
parent
20be7206a0
commit
c4efec762f
9 changed files with 420 additions and 52 deletions
|
@ -9,8 +9,10 @@ namespace hactoolnet
|
|||
{
|
||||
private static readonly CliOption[] CliOptions =
|
||||
{
|
||||
new CliOption("custom", 0, (o, a) => o.RunCustom = true),
|
||||
new CliOption("intype", 't', 1, (o, a) => o.InFileType = ParseFileType(a[0])),
|
||||
new CliOption("raw", 'r', 0, (o, a) => o.Raw = true),
|
||||
new CliOption("verify", 'y', 0, (o, a) => o.Validate = true),
|
||||
new CliOption("keyset", 'k', 1, (o, a) => o.Keyfile = a[0]),
|
||||
new CliOption("titlekeys", 1, (o, a) => o.TitleKeyFile = a[0]),
|
||||
new CliOption("section0", 1, (o, a) => o.SectionOut[0] = a[0]),
|
||||
|
@ -132,6 +134,7 @@ namespace hactoolnet
|
|||
sb.AppendLine("Usage: hactoolnet.exe [options...] <path>");
|
||||
sb.AppendLine("Options:");
|
||||
sb.AppendLine(" -r, --raw Keep raw data, don\'t unpack.");
|
||||
sb.AppendLine(" -y, --verify Verify hashes.");
|
||||
sb.AppendLine(" -k, --keyset Load keys from an external file.");
|
||||
sb.AppendLine(" -t, --intype=type Specify input file type [nca, switchfs]");
|
||||
sb.AppendLine(" --titlekeys <file> Load title keys from an external file.");
|
||||
|
@ -150,6 +153,7 @@ namespace hactoolnet
|
|||
sb.AppendLine(" --listapps List application info.");
|
||||
sb.AppendLine(" --listtitles List title info for all titles.");
|
||||
sb.AppendLine(" --title <title id> Specify title ID to use.");
|
||||
sb.AppendLine(" --outdir <dir> Specify directory path to save title to.");
|
||||
sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path.");
|
||||
|
||||
return sb.ToString();
|
||||
|
|
|
@ -4,9 +4,11 @@ namespace hactoolnet
|
|||
{
|
||||
internal class Options
|
||||
{
|
||||
public bool RunCustom;
|
||||
public string InFile;
|
||||
public FileType InFileType = FileType.Nca;
|
||||
public bool Raw = false;
|
||||
public bool Raw;
|
||||
public bool Validate;
|
||||
public string Keyfile;
|
||||
public string TitleKeyFile;
|
||||
public string[] SectionOut = new string[4];
|
||||
|
|
|
@ -9,13 +9,19 @@ namespace hactoolnet
|
|||
{
|
||||
public static class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
var ctx = new Context();
|
||||
ctx.Options = CliParser.Parse(args);
|
||||
if (ctx.Options == null) return;
|
||||
|
||||
if (ctx.Options.RunCustom)
|
||||
{
|
||||
CustomTask(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var logger = new ProgressBar())
|
||||
{
|
||||
ctx.Logger = logger;
|
||||
|
@ -64,6 +70,11 @@ namespace hactoolnet
|
|||
{
|
||||
nca.ExtractSection(i, ctx.Options.SectionOutDir[i], ctx.Logger);
|
||||
}
|
||||
|
||||
if (ctx.Options.Validate && nca.Sections[i] != null)
|
||||
{
|
||||
nca.VerifySection(i, ctx.Logger);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.Options.ListRomFs && nca.Sections[1] != null)
|
||||
|
@ -75,6 +86,8 @@ namespace hactoolnet
|
|||
ctx.Logger.LogMessage(romfsFile.FullPath);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Logger.LogMessage(nca.Dump());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,6 +129,11 @@ namespace hactoolnet
|
|||
var romfs = new Romfs(title.ProgramNca.OpenSection(1, false));
|
||||
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
|
||||
}
|
||||
|
||||
if (ctx.Options.OutDir != null)
|
||||
{
|
||||
SaveTitle(ctx, switchFs);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OpenKeyset(Context ctx)
|
||||
|
@ -143,6 +161,13 @@ namespace hactoolnet
|
|||
}
|
||||
}
|
||||
|
||||
// For running random stuff
|
||||
// ReSharper disable once UnusedParameter.Local
|
||||
private static void CustomTask(Context ctx)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static void ListSdfs(string[] args)
|
||||
{
|
||||
var sdfs = LoadSdFs(args);
|
||||
|
@ -219,21 +244,32 @@ namespace hactoolnet
|
|||
}
|
||||
}
|
||||
|
||||
static void DecryptTitle(SdFs sdFs, ulong titleId)
|
||||
private static void SaveTitle(Context ctx, SdFs switchFs)
|
||||
{
|
||||
var title = sdFs.Titles[titleId];
|
||||
var dirName = $"{titleId:X16}v{title.Version.Version}";
|
||||
var id = ctx.Options.TitleId;
|
||||
if (id == 0)
|
||||
{
|
||||
ctx.Logger.LogMessage("Title ID must be specified to save title");
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(dirName);
|
||||
if (!switchFs.Titles.TryGetValue(id, out var title))
|
||||
{
|
||||
ctx.Logger.LogMessage($"Could not find title {id:X16}");
|
||||
return;
|
||||
}
|
||||
|
||||
var saveDir = Path.Combine(ctx.Options.OutDir, $"{title.Id:X16}v{title.Version.Version}");
|
||||
Directory.CreateDirectory(saveDir);
|
||||
|
||||
foreach (var nca in title.Ncas)
|
||||
{
|
||||
using (var output = new FileStream(Path.Combine(dirName, nca.Filename), FileMode.Create))
|
||||
using (var progress = new ProgressBar())
|
||||
nca.Stream.Position = 0;
|
||||
var outFile = Path.Combine(saveDir, nca.Filename);
|
||||
ctx.Logger.LogMessage(nca.Filename);
|
||||
using (var outStream = new FileStream(outFile, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
progress.LogMessage($"Writing {nca.Filename}");
|
||||
nca.Stream.Position = 0;
|
||||
nca.Stream.CopyStream(output, nca.Stream.Length, progress);
|
||||
nca.Stream.CopyStream(outStream, nca.Stream.Length, ctx.Logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -318,4 +354,3 @@ namespace hactoolnet
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
295
libhac/Nca.cs
295
libhac/Nca.cs
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using libhac.XTSSharp;
|
||||
|
||||
namespace libhac
|
||||
|
@ -13,6 +14,8 @@ namespace libhac
|
|||
public bool HasRightsId { get; private set; }
|
||||
public int CryptoType { get; private set; }
|
||||
public byte[][] DecryptedKeys { get; } = Util.CreateJaggedArray<byte[][]>(4, 0x10);
|
||||
public byte[] TitleKey { get; }
|
||||
public byte[] TitleKeyDec { get; } = new byte[0x10];
|
||||
public Stream Stream { get; private set; }
|
||||
private bool KeepOpen { get; }
|
||||
|
||||
|
@ -38,7 +41,9 @@ namespace libhac
|
|||
{
|
||||
if (keyset.TitleKeys.TryGetValue(Header.RightsId, out var titleKey))
|
||||
{
|
||||
Crypto.DecryptEcb(keyset.titlekeks[CryptoType], titleKey, DecryptedKeys[2], 0x10);
|
||||
TitleKey = titleKey;
|
||||
Crypto.DecryptEcb(keyset.titlekeks[CryptoType], titleKey, TitleKeyDec, 0x10);
|
||||
DecryptedKeys[2] = TitleKeyDec;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,16 +66,20 @@ namespace libhac
|
|||
|
||||
if (!raw)
|
||||
{
|
||||
switch (sect.Header.FsType)
|
||||
switch (sect.Header.Type)
|
||||
{
|
||||
case SectionFsType.Pfs0:
|
||||
offset = sect.Offset + sect.Pfs0.Pfs0Offset;
|
||||
size = sect.Pfs0.Pfs0Size;
|
||||
case SectionType.Pfs0:
|
||||
offset = sect.Offset + sect.Pfs0.Superblock.Pfs0Offset;
|
||||
size = sect.Pfs0.Superblock.Pfs0Size;
|
||||
break;
|
||||
case SectionFsType.Romfs:
|
||||
offset = sect.Offset + (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1]
|
||||
case SectionType.Romfs:
|
||||
offset = sect.Offset + sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].LogicalOffset;
|
||||
size = sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize;
|
||||
break;
|
||||
case SectionType.Bktr:
|
||||
offset = sect.Offset + sect.Header.Bktr.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1]
|
||||
.LogicalOffset;
|
||||
size = (long)sect.Header.Romfs.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize;
|
||||
size = sect.Header.Bktr.IvfcHeader.LevelHeaders[Romfs.IvfcMaxLevel - 1].HashDataSize;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
@ -88,12 +97,12 @@ namespace libhac
|
|||
case SectionCryptType.CTR:
|
||||
return new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], offset, size, offset, sect.Header.Ctr), false);
|
||||
case SectionCryptType.BKTR:
|
||||
break;
|
||||
return new RandomAccessSectorStream(new AesCtrStream(Stream, DecryptedKeys[2], offset, size, offset, sect.Header.Ctr), false);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return Stream;
|
||||
return new SubStream(Stream, offset, size);
|
||||
}
|
||||
|
||||
private void DecryptHeader(Keyset keyset, Stream stream)
|
||||
|
@ -135,12 +144,41 @@ namespace libhac
|
|||
|
||||
if (sect.Type == SectionType.Pfs0)
|
||||
{
|
||||
sect.Pfs0 = header.Pfs0;
|
||||
sect.Pfs0 = new Pfs0Section();
|
||||
sect.Pfs0.Superblock = header.Pfs0;
|
||||
}
|
||||
else if (sect.Type == SectionType.Romfs)
|
||||
{
|
||||
ProcessIvfcSection(sect);
|
||||
}
|
||||
|
||||
return sect;
|
||||
}
|
||||
|
||||
private void ProcessIvfcSection(NcaSection sect)
|
||||
{
|
||||
sect.Romfs = new RomfsSection();
|
||||
sect.Romfs.Superblock = sect.Header.Romfs;
|
||||
var headers = sect.Romfs.Superblock.IvfcHeader.LevelHeaders;
|
||||
|
||||
for (int i = 0; i < Romfs.IvfcMaxLevel; i++)
|
||||
{
|
||||
var level = new IvfcLevel();
|
||||
sect.Romfs.IvfcLevels[i] = level;
|
||||
var header = headers[i];
|
||||
level.DataOffset = header.LogicalOffset;
|
||||
level.DataSize = header.HashDataSize;
|
||||
level.HashBlockSize = 1 << header.BlockSize;
|
||||
level.HashBlockCount = Util.DivideByRoundUp(level.DataSize, level.HashBlockSize);
|
||||
level.HashSize = level.HashBlockCount * 0x20;
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
level.HashOffset = sect.Romfs.IvfcLevels[i - 1].DataOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateSuperblockHash(int index)
|
||||
{
|
||||
if (Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
@ -165,10 +203,14 @@ namespace libhac
|
|||
case SectionType.Romfs:
|
||||
var ivfc = sect.Header.Romfs.IvfcHeader;
|
||||
expected = ivfc.MasterHash;
|
||||
offset = (long)ivfc.LevelHeaders[0].LogicalOffset;
|
||||
size = 1 << (int)ivfc.LevelHeaders[0].BlockSize;
|
||||
offset = ivfc.LevelHeaders[0].LogicalOffset;
|
||||
size = 1 << ivfc.LevelHeaders[0].BlockSize;
|
||||
break;
|
||||
case SectionType.Bktr:
|
||||
var ivfcBktr = sect.Header.Bktr.IvfcHeader;
|
||||
expected = ivfcBktr.MasterHash;
|
||||
offset = ivfcBktr.LevelHeaders[0].LogicalOffset;
|
||||
size = 1 << ivfcBktr.LevelHeaders[0].BlockSize;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -183,7 +225,91 @@ namespace libhac
|
|||
actual = hash.ComputeHash(hashTable);
|
||||
}
|
||||
|
||||
sect.SuperblockHashValidity = Util.ArraysEqual(expected, actual) ? Validity.Valid : Validity.Invalid;
|
||||
var validity = Util.ArraysEqual(expected, actual) ? Validity.Valid : Validity.Invalid;
|
||||
|
||||
sect.SuperblockHashValidity = validity;
|
||||
if (sect.Type == SectionType.Romfs) sect.Romfs.IvfcLevels[0].HashValidity = validity;
|
||||
}
|
||||
|
||||
public void VerifySection(int index, IProgressReport logger = null)
|
||||
{
|
||||
if (Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index));
|
||||
var sect = Sections[index];
|
||||
var stream = OpenSection(index, true);
|
||||
logger?.LogMessage($"Verifying section {index}...");
|
||||
|
||||
switch (sect.Type)
|
||||
{
|
||||
case SectionType.Invalid:
|
||||
break;
|
||||
case SectionType.Pfs0:
|
||||
VerifyPfs0(stream, sect.Pfs0, logger);
|
||||
break;
|
||||
case SectionType.Romfs:
|
||||
VerifyIvfc(stream, sect.Romfs.IvfcLevels, logger);
|
||||
break;
|
||||
case SectionType.Bktr:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyPfs0(Stream section, Pfs0Section pfs0, IProgressReport logger = null)
|
||||
{
|
||||
var sb = pfs0.Superblock;
|
||||
var table = new byte[sb.HashTableSize];
|
||||
section.Position = sb.HashTableOffset;
|
||||
section.Read(table, 0, table.Length);
|
||||
|
||||
pfs0.Validity = VerifyHashTable(section, table, sb.Pfs0Offset, sb.Pfs0Size, sb.BlockSize, false, logger);
|
||||
}
|
||||
|
||||
private void VerifyIvfc(Stream section, IvfcLevel[] levels, IProgressReport logger = null)
|
||||
{
|
||||
for (int i = 1; i < levels.Length; i++)
|
||||
{
|
||||
logger?.LogMessage($" Verifying IVFC Level {i}...");
|
||||
var level = levels[i];
|
||||
var table = new byte[level.HashSize];
|
||||
section.Position = level.HashOffset;
|
||||
section.Read(table, 0, table.Length);
|
||||
level.HashValidity =
|
||||
VerifyHashTable(section, table, level.DataOffset, level.DataSize, level.HashBlockSize, true, logger);
|
||||
}
|
||||
}
|
||||
|
||||
private Validity VerifyHashTable(Stream section, byte[] hashTable, long dataOffset, long dataLen, long blockSize, bool isFinalBlockFull, IProgressReport logger = null)
|
||||
{
|
||||
const int hashSize = 0x20;
|
||||
var currentBlock = new byte[blockSize];
|
||||
var expectedHash = new byte[hashSize];
|
||||
var blockCount = Util.DivideByRoundUp(dataLen, blockSize);
|
||||
int curBlockSize = (int)blockSize;
|
||||
section.Position = dataOffset;
|
||||
logger?.SetTotal(blockCount);
|
||||
|
||||
using (SHA256 sha256 = SHA256.Create())
|
||||
{
|
||||
for (long i = 0; i < blockCount; i++)
|
||||
{
|
||||
var remaining = (dataLen - i * blockSize);
|
||||
if (remaining < blockSize)
|
||||
{
|
||||
Array.Clear(currentBlock, 0, currentBlock.Length);
|
||||
if (!isFinalBlockFull) curBlockSize = (int)remaining;
|
||||
}
|
||||
Array.Copy(hashTable, i * hashSize, expectedHash, 0, hashSize);
|
||||
section.Read(currentBlock, 0, curBlockSize);
|
||||
var actualHash = sha256.ComputeHash(currentBlock, 0, curBlockSize);
|
||||
|
||||
if (!Util.ArraysEqual(expectedHash, actualHash))
|
||||
{
|
||||
return Validity.Invalid;
|
||||
}
|
||||
logger?.ReportAdd(1);
|
||||
}
|
||||
}
|
||||
|
||||
return Validity.Valid;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -205,18 +331,20 @@ namespace libhac
|
|||
public long Size { get; set; }
|
||||
public Validity SuperblockHashValidity { get; set; }
|
||||
|
||||
public Pfs0Superblock Pfs0 { get; set; }
|
||||
public Pfs0Section Pfs0 { get; set; }
|
||||
public RomfsSection Romfs { get; set; }
|
||||
}
|
||||
|
||||
public static class NcaExtensions
|
||||
{
|
||||
public static void ExportSection(this Nca nca, int index, string filename, bool raw = false, IProgressReport logger = null)
|
||||
{
|
||||
if(index < 0 || index > 3) throw new IndexOutOfRangeException();
|
||||
if (index < 0 || index > 3) throw new IndexOutOfRangeException();
|
||||
if (nca.Sections[index] == null) return;
|
||||
|
||||
var section = nca.OpenSection(index, raw);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filename));
|
||||
var dir = Path.GetDirectoryName(filename);
|
||||
if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir);
|
||||
|
||||
using (var outFile = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
|
@ -226,7 +354,7 @@ namespace libhac
|
|||
|
||||
public static void ExtractSection(this Nca nca, int index, string outputDir, IProgressReport logger = null)
|
||||
{
|
||||
if(index < 0 || index > 3) throw new IndexOutOfRangeException();
|
||||
if (index < 0 || index > 3) throw new IndexOutOfRangeException();
|
||||
if (nca.Sections[index] == null) return;
|
||||
|
||||
var section = nca.Sections[index];
|
||||
|
@ -248,5 +376,136 @@ 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.Type);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,13 @@ namespace libhac
|
|||
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 DistributionType 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 TitleVersion SdkVersion; // What SDK was this built with?
|
||||
public byte CryptoType2; // Which keyblob (field 2)
|
||||
public byte[] RightsId;
|
||||
public string Name;
|
||||
|
@ -33,7 +33,7 @@ namespace libhac
|
|||
head.Signature2 = reader.ReadBytes(0x100);
|
||||
head.Magic = reader.ReadAscii(4);
|
||||
if (head.Magic != "NCA3") throw new InvalidDataException("Not an NCA3 file");
|
||||
head.Distribution = reader.ReadByte();
|
||||
head.Distribution = (DistributionType)reader.ReadByte();
|
||||
head.ContentType = (ContentType)reader.ReadByte();
|
||||
head.CryptoType = reader.ReadByte();
|
||||
head.KaekInd = reader.ReadByte();
|
||||
|
@ -41,7 +41,7 @@ namespace libhac
|
|||
head.TitleId = reader.ReadUInt64();
|
||||
reader.BaseStream.Position += 4;
|
||||
|
||||
head.SdkVersion = reader.ReadUInt32();
|
||||
head.SdkVersion = new TitleVersion(reader.ReadUInt32());
|
||||
head.CryptoType2 = reader.ReadByte();
|
||||
reader.BaseStream.Position += 0xF;
|
||||
|
||||
|
@ -188,16 +188,16 @@ namespace libhac
|
|||
|
||||
public class IvfcLevelHeader
|
||||
{
|
||||
public ulong LogicalOffset;
|
||||
public ulong HashDataSize;
|
||||
public uint BlockSize;
|
||||
public long LogicalOffset;
|
||||
public long HashDataSize;
|
||||
public int BlockSize;
|
||||
public uint Reserved;
|
||||
|
||||
public IvfcLevelHeader(BinaryReader reader)
|
||||
{
|
||||
LogicalOffset = reader.ReadUInt64();
|
||||
HashDataSize = reader.ReadUInt64();
|
||||
BlockSize = reader.ReadUInt32();
|
||||
LogicalOffset = reader.ReadInt64();
|
||||
HashDataSize = reader.ReadInt64();
|
||||
BlockSize = reader.ReadInt32();
|
||||
Reserved = reader.ReadUInt32();
|
||||
}
|
||||
}
|
||||
|
@ -256,6 +256,18 @@ namespace libhac
|
|||
}
|
||||
}
|
||||
|
||||
public class Pfs0Section
|
||||
{
|
||||
public Pfs0Superblock Superblock { get; set; }
|
||||
public Validity Validity { get; set; }
|
||||
}
|
||||
|
||||
public class RomfsSection
|
||||
{
|
||||
public RomfsSuperblock Superblock { get; set; }
|
||||
public IvfcLevel[] IvfcLevels { get; set; } = new IvfcLevel[Romfs.IvfcMaxLevel];
|
||||
}
|
||||
|
||||
public enum ContentType
|
||||
{
|
||||
Program,
|
||||
|
@ -266,6 +278,12 @@ namespace libhac
|
|||
Unknown
|
||||
}
|
||||
|
||||
public enum DistributionType
|
||||
{
|
||||
Download,
|
||||
Gamecard
|
||||
}
|
||||
|
||||
public enum SectionCryptType
|
||||
{
|
||||
None = 1,
|
||||
|
|
|
@ -137,7 +137,8 @@ namespace libhac
|
|||
{
|
||||
var stream = pfs0.OpenFile(file);
|
||||
var outName = Path.Combine(outDir, file.Name);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outName));
|
||||
var dir = Path.GetDirectoryName(outName);
|
||||
if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir);
|
||||
|
||||
using (var outFile = new FileStream(outName, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
|
|
|
@ -230,11 +230,13 @@ namespace libhac
|
|||
|
||||
public class IvfcLevel
|
||||
{
|
||||
public ulong DataOffset { get; set; }
|
||||
public ulong DataSize { get; set; }
|
||||
public ulong HashOffset { get; set; }
|
||||
public ulong HashBlockSize { get; set; }
|
||||
public ulong HashBlockCount { get; set; }
|
||||
public long DataOffset { get; set; }
|
||||
public long DataSize { get; set; }
|
||||
public long HashOffset { get; set; }
|
||||
public long HashSize { get; set; }
|
||||
public long HashBlockSize { get; set; }
|
||||
public long HashBlockCount { get; set; }
|
||||
public Validity HashValidity { get; set; }
|
||||
}
|
||||
|
||||
public static class RomfsExtensions
|
||||
|
@ -245,7 +247,8 @@ namespace libhac
|
|||
{
|
||||
var stream = romfs.OpenFile(file);
|
||||
var outName = outDir + file.FullPath;
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outName));
|
||||
var dir = Path.GetDirectoryName(outName);
|
||||
if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir);
|
||||
|
||||
using (var outFile = new FileStream(outName, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
|
|
|
@ -77,7 +77,7 @@ namespace libhac
|
|||
Console.WriteLine($"{ex.Message} {file}");
|
||||
}
|
||||
|
||||
if (nca != null) Ncas.Add(nca.NcaId, nca);
|
||||
if (nca?.NcaId != null) Ncas.Add(nca.NcaId, nca);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,8 +129,6 @@ namespace libhac
|
|||
{
|
||||
var romfs = new Romfs(title.ControlNca.OpenSection(0, false));
|
||||
var control = romfs.GetFile("/control.nacp");
|
||||
Directory.CreateDirectory("control");
|
||||
File.WriteAllBytes($"control/{title.Id:X16}.nacp", control);
|
||||
|
||||
var reader = new BinaryReader(new MemoryStream(control));
|
||||
title.Control = new Nacp(reader);
|
||||
|
|
|
@ -287,6 +287,54 @@ namespace libhac
|
|||
|
||||
return value + multiple - value % multiple;
|
||||
}
|
||||
|
||||
public static int DivideByRoundUp(int value, int divisor) => (value + divisor - 1) / divisor;
|
||||
public static long DivideByRoundUp(long value, long divisor) => (value + divisor - 1) / divisor;
|
||||
|
||||
public static void MemDump(this StringBuilder sb, string prefix, byte[] data)
|
||||
{
|
||||
|
||||
int max = 32;
|
||||
var remaining = data.Length;
|
||||
bool first = true;
|
||||
int offset = 0;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
max = Math.Min(max, remaining);
|
||||
|
||||
if (first)
|
||||
{
|
||||
sb.Append(prefix);
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(' ', prefix.Length);
|
||||
}
|
||||
|
||||
for (int i = 0; i < max; i++)
|
||||
{
|
||||
sb.Append($"{data[offset++]:X2}");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
remaining -= max;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetKeyRevisionSummary(int revision)
|
||||
{
|
||||
switch (revision)
|
||||
{
|
||||
case 0: return "1.0.0-2.3.0";
|
||||
case 1: return "3.0.0";
|
||||
case 2: return "3.0.1-3.0.2";
|
||||
case 3: return "4.0.0-4.1.0";
|
||||
case 4: return "5.0.0";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ByteArray128BitComparer : EqualityComparer<byte[]>
|
||||
|
|
Loading…
Reference in a new issue