Merge remote-tracking branch 'upstream/master'

This commit is contained in:
jonnysp 2018-10-08 06:01:08 +02:00
commit 9cff0737ed
15 changed files with 333 additions and 318 deletions

View file

@ -23,12 +23,12 @@ namespace LibHac
Patch = patchRomfs ?? throw new NullReferenceException($"{nameof(patchRomfs)} cannot be null"); Patch = patchRomfs ?? throw new NullReferenceException($"{nameof(patchRomfs)} cannot be null");
Base = baseRomfs ?? throw new NullReferenceException($"{nameof(baseRomfs)} cannot be null"); Base = baseRomfs ?? throw new NullReferenceException($"{nameof(baseRomfs)} cannot be null");
IvfcLevelHeader level5 = section.Header.Bktr.IvfcHeader.LevelHeaders[5]; IvfcLevelHeader level5 = section.Header.IvfcInfo.LevelHeaders[5];
Length = level5.LogicalOffset + level5.HashDataSize; Length = level5.LogicalOffset + level5.HashDataSize;
using (var reader = new BinaryReader(patchRomfs, Encoding.Default, true)) using (var reader = new BinaryReader(patchRomfs, Encoding.Default, true))
{ {
patchRomfs.Position = section.Header.Bktr.RelocationHeader.Offset; patchRomfs.Position = section.Header.BktrInfo.RelocationHeader.Offset;
RelocationBlock = new RelocationBlock(reader); RelocationBlock = new RelocationBlock(reader);
} }

View file

@ -8,15 +8,15 @@ namespace LibHac
{ {
public class BktrCryptoStream : Aes128CtrStream public class BktrCryptoStream : Aes128CtrStream
{ {
public SubsectionBlock SubsectionBlock { get; } public AesSubsectionBlock AesSubsectionBlock { get; }
private List<SubsectionEntry> SubsectionEntries { get; } = new List<SubsectionEntry>(); private List<AesSubsectionEntry> SubsectionEntries { get; } = new List<AesSubsectionEntry>();
private List<long> SubsectionOffsets { get; } private List<long> SubsectionOffsets { get; }
private SubsectionEntry CurrentEntry { get; set; } private AesSubsectionEntry CurrentEntry { get; set; }
public BktrCryptoStream(Stream baseStream, byte[] key, long offset, long length, long counterOffset, byte[] ctrHi, BktrSuperblock bktr) public BktrCryptoStream(Stream baseStream, byte[] key, long offset, long length, long counterOffset, byte[] ctrHi, BktrPatchInfo bktr)
: base(baseStream, key, offset, length, counterOffset, ctrHi) : base(baseStream, key, offset, length, counterOffset, ctrHi)
{ {
BktrHeader header = bktr.SubsectionHeader; BktrHeader header = bktr.EncryptionHeader;
byte[] subsectionBytes; byte[] subsectionBytes;
using (var streamDec = new RandomAccessSectorStream(new Aes128CtrStream(baseStream, key, offset, length, counterOffset, ctrHi))) using (var streamDec = new RandomAccessSectorStream(new Aes128CtrStream(baseStream, key, offset, length, counterOffset, ctrHi)))
{ {
@ -27,16 +27,16 @@ namespace LibHac
using (var reader = new BinaryReader(new MemoryStream(subsectionBytes))) using (var reader = new BinaryReader(new MemoryStream(subsectionBytes)))
{ {
SubsectionBlock = new SubsectionBlock(reader); AesSubsectionBlock = new AesSubsectionBlock(reader);
} }
foreach (SubsectionBucket bucket in SubsectionBlock.Buckets) foreach (AesSubsectionBucket bucket in AesSubsectionBlock.Buckets)
{ {
SubsectionEntries.AddRange(bucket.Entries); SubsectionEntries.AddRange(bucket.Entries);
} }
// Add a subsection for the BKTR headers to make things easier // Add a subsection for the BKTR headers to make things easier
var headerSubsection = new SubsectionEntry var headerSubsection = new AesSubsectionEntry
{ {
Offset = bktr.RelocationHeader.Offset, Offset = bktr.RelocationHeader.Offset,
Counter = (uint)(ctrHi[4] << 24 | ctrHi[5] << 16 | ctrHi[6] << 8 | ctrHi[7]), Counter = (uint)(ctrHi[4] << 24 | ctrHi[5] << 16 | ctrHi[6] << 8 | ctrHi[7]),
@ -92,7 +92,7 @@ namespace LibHac
return totalBytesRead; return totalBytesRead;
} }
private SubsectionEntry GetSubsectionEntry(long offset) private AesSubsectionEntry GetSubsectionEntry(long offset)
{ {
int index = SubsectionOffsets.BinarySearch(offset); int index = SubsectionOffsets.BinarySearch(offset);
if (index < 0) index = ~index - 1; if (index < 0) index = ~index - 1;

View file

@ -75,15 +75,15 @@ namespace LibHac
} }
} }
public class SubsectionBlock public class AesSubsectionBlock
{ {
public uint Field0; public uint Field0;
public int BucketCount; public int BucketCount;
public long Size; public long Size;
public long[] BaseOffsets; public long[] BaseOffsets;
public SubsectionBucket[] Buckets; public AesSubsectionBucket[] Buckets;
public SubsectionBlock(BinaryReader reader) public AesSubsectionBlock(BinaryReader reader)
{ {
long start = reader.BaseStream.Position; long start = reader.BaseStream.Position;
@ -91,7 +91,7 @@ namespace LibHac
BucketCount = reader.ReadInt32(); BucketCount = reader.ReadInt32();
Size = reader.ReadInt64(); Size = reader.ReadInt64();
BaseOffsets = new long[BucketCount]; BaseOffsets = new long[BucketCount];
Buckets = new SubsectionBucket[BucketCount]; Buckets = new AesSubsectionBucket[BucketCount];
for (int i = 0; i < BucketCount; i++) for (int i = 0; i < BucketCount; i++)
{ {
@ -102,47 +102,47 @@ namespace LibHac
for (int i = 0; i < BucketCount; i++) for (int i = 0; i < BucketCount; i++)
{ {
Buckets[i] = new SubsectionBucket(reader); Buckets[i] = new AesSubsectionBucket(reader);
} }
} }
} }
public class SubsectionBucket public class AesSubsectionBucket
{ {
public int BucketNum; public int BucketNum;
public int EntryCount; public int EntryCount;
public long VirtualOffsetEnd; public long VirtualOffsetEnd;
public SubsectionEntry[] Entries; public AesSubsectionEntry[] Entries;
public SubsectionBucket(BinaryReader reader) public AesSubsectionBucket(BinaryReader reader)
{ {
long start = reader.BaseStream.Position; long start = reader.BaseStream.Position;
BucketNum = reader.ReadInt32(); BucketNum = reader.ReadInt32();
EntryCount = reader.ReadInt32(); EntryCount = reader.ReadInt32();
VirtualOffsetEnd = reader.ReadInt64(); VirtualOffsetEnd = reader.ReadInt64();
Entries = new SubsectionEntry[EntryCount]; Entries = new AesSubsectionEntry[EntryCount];
for (int i = 0; i < EntryCount; i++) for (int i = 0; i < EntryCount; i++)
{ {
Entries[i] = new SubsectionEntry(reader); Entries[i] = new AesSubsectionEntry(reader);
} }
reader.BaseStream.Position = start + 0x4000; reader.BaseStream.Position = start + 0x4000;
} }
} }
public class SubsectionEntry public class AesSubsectionEntry
{ {
public long Offset; public long Offset;
public uint Field8; public uint Field8;
public uint Counter; public uint Counter;
public SubsectionEntry Next; public AesSubsectionEntry Next;
public long OffsetEnd; public long OffsetEnd;
public SubsectionEntry() { } public AesSubsectionEntry() { }
public SubsectionEntry(BinaryReader reader) public AesSubsectionEntry(BinaryReader reader)
{ {
Offset = reader.ReadInt64(); Offset = reader.ReadInt64();
Field8 = reader.ReadUInt32(); Field8 = reader.ReadUInt32();

View file

@ -19,7 +19,7 @@ namespace LibHac
private readonly SHA256 _hash = SHA256.Create(); private readonly SHA256 _hash = SHA256.Create();
public IntegrityVerificationStream(IntegrityVerificationInfo info, Stream hashStream, bool enableIntegrityChecks) public IntegrityVerificationStream(IntegrityVerificationInfo info, Stream hashStream, bool enableIntegrityChecks)
: base(info.Data, 1 << info.BlockSizePower) : base(info.Data, info.BlockSize)
{ {
HashStream = hashStream; HashStream = hashStream;
EnableIntegrityChecks = enableIntegrityChecks; EnableIntegrityChecks = enableIntegrityChecks;
@ -61,6 +61,9 @@ namespace LibHac
HashStream.Read(_hashBuffer, 0, DigestSize); HashStream.Read(_hashBuffer, 0, DigestSize);
int bytesRead = base.Read(buffer, 0, count); int bytesRead = base.Read(buffer, 0, count);
int bytesToHash = SectorSize;
if (bytesRead == 0) return 0;
// If a hash is zero the data for the entire block is zero // If a hash is zero the data for the entire block is zero
if (Type == IntegrityStreamType.Save && _hashBuffer.IsEmpty()) if (Type == IntegrityStreamType.Save && _hashBuffer.IsEmpty())
@ -73,6 +76,12 @@ namespace LibHac
{ {
// Pad out unused portion of block // Pad out unused portion of block
Array.Clear(buffer, bytesRead, SectorSize - bytesRead); Array.Clear(buffer, bytesRead, SectorSize - bytesRead);
// Partition FS hashes don't pad out an incomplete block
if (Type == IntegrityStreamType.PartitionFs)
{
bytesToHash = bytesRead;
}
} }
if (!EnableIntegrityChecks) return bytesRead; if (!EnableIntegrityChecks) return bytesRead;
@ -84,7 +93,7 @@ namespace LibHac
_hash.TransformBlock(Salt, 0, Salt.Length, null, 0); _hash.TransformBlock(Salt, 0, Salt.Length, null, 0);
} }
_hash.TransformBlock(buffer, 0, SectorSize, null, 0); _hash.TransformBlock(buffer, 0, bytesToHash, null, 0);
_hash.TransformFinalBlock(buffer, 0, 0); _hash.TransformFinalBlock(buffer, 0, 0);
byte[] hash = _hash.Hash; byte[] hash = _hash.Hash;
@ -119,7 +128,7 @@ namespace LibHac
public class IntegrityVerificationInfo public class IntegrityVerificationInfo
{ {
public Stream Data { get; set; } public Stream Data { get; set; }
public int BlockSizePower { get; set; } public int BlockSize { get; set; }
public byte[] Salt { get; set; } public byte[] Salt { get; set; }
public IntegrityStreamType Type { get; set; } public IntegrityStreamType Type { get; set; }
} }
@ -127,6 +136,7 @@ namespace LibHac
public enum IntegrityStreamType public enum IntegrityStreamType
{ {
Save, Save,
RomFs RomFs,
PartitionFs
} }
} }

View file

@ -396,6 +396,8 @@ namespace LibHac
public static string PrintKeys(Keyset keyset, Dictionary<string, KeyValue> dict) public static string PrintKeys(Keyset keyset, Dictionary<string, KeyValue> dict)
{ {
if(dict.Count == 0) return string.Empty;
var sb = new StringBuilder(); var sb = new StringBuilder();
int maxNameLength = dict.Values.Max(x => x.Name.Length); int maxNameLength = dict.Values.Max(x => x.Name.Length);
@ -429,13 +431,10 @@ namespace LibHac
public static string PrintTitleKeys(Keyset keyset) public static string PrintTitleKeys(Keyset keyset)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
int maxNameLength = keyset.TitleKeys.Values.Max(x => x.Length);
foreach (KeyValuePair<byte[], byte[]> kv in keyset.TitleKeys) foreach (KeyValuePair<byte[], byte[]> kv in keyset.TitleKeys)
{ {
byte[] key = kv.Key; string line = $"{kv.Key.ToHexString()} = {kv.Value.ToHexString()}";
byte[] value = kv.Value;
string line = $"{key.ToHexString().PadRight(maxNameLength)} = {value.ToHexString()}";
sb.AppendLine(line); sb.AppendLine(line);
} }

View file

@ -16,7 +16,6 @@ namespace LibHac
public byte[][] DecryptedKeys { get; } = Util.CreateJaggedArray<byte[][]>(4, 0x10); public byte[][] DecryptedKeys { get; } = Util.CreateJaggedArray<byte[][]>(4, 0x10);
public byte[] TitleKey { get; } public byte[] TitleKey { get; }
public byte[] TitleKeyDec { get; } = new byte[0x10]; public byte[] TitleKeyDec { get; } = new byte[0x10];
private Stream Stream { get; }
private SharedStreamSource StreamSource { get; } private SharedStreamSource StreamSource { get; }
private bool KeepOpen { get; } private bool KeepOpen { get; }
private Nca BaseNca { get; set; } private Nca BaseNca { get; set; }
@ -27,8 +26,7 @@ namespace LibHac
{ {
stream.Position = 0; stream.Position = 0;
KeepOpen = keepOpen; KeepOpen = keepOpen;
Stream = stream; StreamSource = new SharedStreamSource(stream, keepOpen);
StreamSource = new SharedStreamSource(stream);
DecryptHeader(keyset, stream); DecryptHeader(keyset, stream);
CryptoType = Math.Max(Header.CryptoType, Header.CryptoType2); CryptoType = Math.Max(Header.CryptoType, Header.CryptoType2);
@ -50,7 +48,8 @@ namespace LibHac
} }
else else
{ {
throw new MissingKeyException("A required key is missing.", $"{Header.RightsId.ToHexString()}", KeyType.Title); // todo enable key check when opening a section
// throw new MissingKeyException("A required key is missing.", $"{Header.RightsId.ToHexString()}", KeyType.Title);
} }
} }
@ -84,24 +83,24 @@ namespace LibHac
NcaSection sect = Sections[index]; NcaSection sect = Sections[index];
if (sect == null) throw new ArgumentOutOfRangeException(nameof(index)); if (sect == null) throw new ArgumentOutOfRangeException(nameof(index));
if (sect.SuperblockHashValidity == Validity.Invalid) return null; //if (sect.SuperblockHashValidity == Validity.Invalid) return null;
long offset = sect.Offset; long offset = sect.Offset;
long size = sect.Size; long size = sect.Size;
Stream rawStream = StreamSource.CreateStream(offset, size); Stream rawStream = StreamSource.CreateStream(offset, size);
switch (sect.Header.CryptType) switch (sect.Header.EncryptionType)
{ {
case SectionCryptType.None: case NcaEncryptionType.None:
return rawStream; return rawStream;
case SectionCryptType.XTS: case NcaEncryptionType.XTS:
throw new NotImplementedException("NCA sections using XTS are not supported"); throw new NotImplementedException("NCA sections using XTS are not supported");
case SectionCryptType.CTR: case NcaEncryptionType.AesCtr:
return new RandomAccessSectorStream(new Aes128CtrStream(rawStream, DecryptedKeys[2], offset, sect.Header.Ctr), false); return new RandomAccessSectorStream(new Aes128CtrStream(rawStream, DecryptedKeys[2], offset, sect.Header.Ctr), false);
case SectionCryptType.BKTR: case NcaEncryptionType.AesCtrEx:
rawStream = new RandomAccessSectorStream( rawStream = new RandomAccessSectorStream(
new BktrCryptoStream(rawStream, DecryptedKeys[2], 0, size, offset, sect.Header.Ctr, sect.Header.Bktr), new BktrCryptoStream(rawStream, DecryptedKeys[2], 0, size, offset, sect.Header.Ctr, sect.Header.BktrInfo),
false); false);
if (BaseNca == null) return rawStream; if (BaseNca == null) return rawStream;
@ -121,30 +120,15 @@ namespace LibHac
Stream rawStream = OpenRawSection(index); Stream rawStream = OpenRawSection(index);
NcaSection sect = Sections[index]; NcaSection sect = Sections[index];
if (raw) return rawStream; if (raw || rawStream == null) return rawStream;
switch (sect.Header.Type) switch (sect.Header.Type)
{ {
case SectionType.Pfs0: case SectionType.Pfs0:
PfsSuperblock pfs0Superblock = sect.Pfs0.Superblock; return InitIvfcForPartitionfs(sect.Header.Sha256Info, new SharedStreamSource(rawStream), enableIntegrityChecks);
return new SubStream(rawStream, pfs0Superblock.Pfs0Offset, pfs0Superblock.Pfs0Size);
case SectionType.Romfs: case SectionType.Romfs:
case SectionType.Bktr: case SectionType.Bktr:
return InitIvfcForRomfs(sect.Header.IvfcInfo, new SharedStreamSource(rawStream), enableIntegrityChecks);
var romfsStreamSource = new SharedStreamSource(rawStream);
IvfcHeader ivfc;
if (sect.Header.Type == SectionType.Romfs)
{
ivfc = sect.Header.Romfs.IvfcHeader;
}
else
{
ivfc = sect.Header.Bktr.IvfcHeader;
}
return InitIvfcForRomfs(ivfc, romfsStreamSource, enableIntegrityChecks);
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
@ -159,7 +143,7 @@ namespace LibHac
initInfo[0] = new IntegrityVerificationInfo initInfo[0] = new IntegrityVerificationInfo
{ {
Data = new MemoryStream(ivfc.MasterHash), Data = new MemoryStream(ivfc.MasterHash),
BlockSizePower = 0 BlockSize = 0
}; };
for (int i = 1; i < ivfc.NumLevels; i++) for (int i = 1; i < ivfc.NumLevels; i++)
@ -170,7 +154,7 @@ namespace LibHac
initInfo[i] = new IntegrityVerificationInfo initInfo[i] = new IntegrityVerificationInfo
{ {
Data = data, Data = data,
BlockSizePower = level.BlockSize, BlockSize = 1 << level.BlockSizePower,
Type = IntegrityStreamType.RomFs Type = IntegrityStreamType.RomFs
}; };
} }
@ -178,6 +162,39 @@ namespace LibHac
return new HierarchicalIntegrityVerificationStream(initInfo, enableIntegrityChecks); return new HierarchicalIntegrityVerificationStream(initInfo, enableIntegrityChecks);
} }
private static Stream InitIvfcForPartitionfs(Sha256Info sb,
SharedStreamSource pfsStreamSource, bool enableIntegrityChecks)
{
SharedStream hashStream = pfsStreamSource.CreateStream(sb.HashTableOffset, sb.HashTableSize);
SharedStream dataStream = pfsStreamSource.CreateStream(sb.DataOffset, sb.DataSize);
var initInfo = new IntegrityVerificationInfo[3];
// Set the master hash
initInfo[0] = new IntegrityVerificationInfo
{
Data = new MemoryStream(sb.MasterHash),
BlockSize = 0,
Type = IntegrityStreamType.PartitionFs
};
initInfo[1] = new IntegrityVerificationInfo
{
Data = hashStream,
BlockSize = (int)sb.HashTableSize,
Type = IntegrityStreamType.PartitionFs
};
initInfo[2] = new IntegrityVerificationInfo
{
Data = dataStream,
BlockSize = sb.BlockSize,
Type = IntegrityStreamType.PartitionFs
};
return new HierarchicalIntegrityVerificationStream(initInfo, enableIntegrityChecks);
}
public void SetBaseNca(Nca baseNca) => BaseNca = baseNca; public void SetBaseNca(Nca baseNca) => BaseNca = baseNca;
private void DecryptHeader(Keyset keyset, Stream stream) private void DecryptHeader(Keyset keyset, Stream stream)
@ -217,46 +234,12 @@ namespace LibHac
sect.Header = header; sect.Header = header;
sect.Type = header.Type; sect.Type = header.Type;
if (sect.Type == SectionType.Pfs0)
{
sect.Pfs0 = new Pfs0Section();
sect.Pfs0.Superblock = header.Pfs;
}
else if (sect.Type == SectionType.Romfs)
{
ProcessIvfcSection(sect);
}
return sect; return sect;
} }
private void ProcessIvfcSection(NcaSection sect)
{
sect.Romfs = new RomfsSection();
sect.Romfs.Superblock = sect.Header.Romfs;
IvfcLevelHeader[] headers = sect.Romfs.Superblock.IvfcHeader.LevelHeaders;
for (int i = 0; i < Romfs.IvfcMaxLevel; i++)
{
var level = new IvfcLevel();
sect.Romfs.IvfcLevels[i] = level;
IvfcLevelHeader 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 CheckBktrKey(NcaSection sect) private void CheckBktrKey(NcaSection sect)
{ {
long offset = sect.Header.Bktr.SubsectionHeader.Offset; long offset = sect.Header.BktrInfo.EncryptionHeader.Offset;
using (var streamDec = new RandomAccessSectorStream(new Aes128CtrStream(GetStream(), DecryptedKeys[2], sect.Offset, sect.Size, sect.Offset, sect.Header.Ctr))) using (var streamDec = new RandomAccessSectorStream(new Aes128CtrStream(GetStream(), DecryptedKeys[2], sect.Offset, sect.Size, sect.Offset, sect.Header.Ctr)))
{ {
var reader = new BinaryReader(streamDec); var reader = new BinaryReader(streamDec);
@ -284,16 +267,16 @@ namespace LibHac
case SectionType.Invalid: case SectionType.Invalid:
break; break;
case SectionType.Pfs0: case SectionType.Pfs0:
PfsSuperblock pfs0 = sect.Header.Pfs; Sha256Info pfs0 = sect.Header.Sha256Info;
expected = pfs0.MasterHash; expected = pfs0.MasterHash;
offset = pfs0.HashTableOffset; offset = pfs0.HashTableOffset;
size = pfs0.HashTableSize; size = pfs0.HashTableSize;
break; break;
case SectionType.Romfs: case SectionType.Romfs:
IvfcHeader ivfc = sect.Header.Romfs.IvfcHeader; IvfcHeader ivfc = sect.Header.IvfcInfo;
expected = ivfc.MasterHash; expected = ivfc.MasterHash;
offset = ivfc.LevelHeaders[0].LogicalOffset; offset = ivfc.LevelHeaders[0].LogicalOffset;
size = 1 << ivfc.LevelHeaders[0].BlockSize; size = 1 << ivfc.LevelHeaders[0].BlockSizePower;
break; break;
case SectionType.Bktr: case SectionType.Bktr:
CheckBktrKey(sect); CheckBktrKey(sect);
@ -309,7 +292,7 @@ namespace LibHac
stream.Read(hashTable, 0, hashTable.Length); stream.Read(hashTable, 0, hashTable.Length);
sect.SuperblockHashValidity = Crypto.CheckMemoryHashTable(hashTable, expected, 0, hashTable.Length); sect.SuperblockHashValidity = Crypto.CheckMemoryHashTable(hashTable, expected, 0, hashTable.Length);
if (sect.Type == SectionType.Romfs) sect.Romfs.IvfcLevels[0].HashValidity = sect.SuperblockHashValidity; // todo if (sect.Type == SectionType.Romfs) sect.Romfs.IvfcLevels[0].HashValidity = sect.SuperblockHashValidity;
} }
public void VerifySection(int index, IProgressReport logger = null) public void VerifySection(int index, IProgressReport logger = null)
@ -324,83 +307,27 @@ namespace LibHac
case SectionType.Invalid: case SectionType.Invalid:
break; break;
case SectionType.Pfs0: case SectionType.Pfs0:
VerifyPfs0(stream, sect.Pfs0, logger); // todo VerifyPfs0(stream, sect.Pfs0, logger);
break; break;
case SectionType.Romfs: case SectionType.Romfs:
VerifyIvfc(stream, sect.Romfs.IvfcLevels, logger); // todo VerifyIvfc(stream, sect.Romfs.IvfcLevels, logger);
break; break;
case SectionType.Bktr: case SectionType.Bktr:
break; break;
} }
} }
private void VerifyPfs0(Stream section, Pfs0Section pfs0, IProgressReport logger = null)
{
PfsSuperblock 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}...");
IvfcLevel 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];
long blockCount = Util.DivideByRoundUp(dataLen, blockSize);
int curBlockSize = (int)blockSize;
section.Position = dataOffset;
logger?.SetTotal(blockCount);
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);
if (Crypto.CheckMemoryHashTable(currentBlock, expectedHash, 0, curBlockSize) == Validity.Invalid)
{
return Validity.Invalid;
}
logger?.ReportAdd(1);
}
return Validity.Valid;
}
public void Dispose() public void Dispose()
{ {
if (!KeepOpen) if (!KeepOpen)
{ {
Stream?.Dispose(); StreamSource?.Dispose();
} }
} }
} }
public class NcaSection public class NcaSection
{ {
public Stream Stream { get; set; }
public NcaFsHeader Header { get; set; } public NcaFsHeader Header { get; set; }
public SectionType Type { get; set; } public SectionType Type { get; set; }
public int SectionNum { get; set; } public int SectionNum { get; set; }
@ -408,8 +335,6 @@ namespace LibHac
public long Size { get; set; } public long Size { get; set; }
public Validity SuperblockHashValidity { get; set; } public Validity SuperblockHashValidity { get; set; }
public Pfs0Section Pfs0 { get; set; }
public RomfsSection Romfs { get; set; }
public bool IsExefs { get; internal set; } public bool IsExefs { get; internal set; }
} }

View file

@ -84,48 +84,66 @@ namespace LibHac
public class NcaFsHeader public class NcaFsHeader
{ {
public byte Field0; public short Version;
public byte Field1; public NcaFormatType FormatType;
public SectionPartitionType PartitionType; public NcaHashType HashType;
public SectionFsType FsType; public NcaEncryptionType EncryptionType;
public SectionCryptType CryptType;
public SectionType Type; public SectionType Type;
public PfsSuperblock Pfs; public IvfcHeader IvfcInfo;
public RomfsSuperblock Romfs; public Sha256Info Sha256Info;
public BktrSuperblock Bktr; public BktrPatchInfo BktrInfo;
public byte[] Ctr; public byte[] Ctr;
public NcaFsHeader(BinaryReader reader) public NcaFsHeader(BinaryReader reader)
{ {
long start = reader.BaseStream.Position; long start = reader.BaseStream.Position;
Field0 = reader.ReadByte(); Version = reader.ReadInt16();
Field1 = reader.ReadByte(); FormatType = (NcaFormatType)reader.ReadByte();
PartitionType = (SectionPartitionType)reader.ReadByte(); HashType = (NcaHashType)reader.ReadByte();
FsType = (SectionFsType)reader.ReadByte(); EncryptionType = (NcaEncryptionType)reader.ReadByte();
CryptType = (SectionCryptType)reader.ReadByte();
reader.BaseStream.Position += 3; reader.BaseStream.Position += 3;
if (PartitionType == SectionPartitionType.Pfs0 && FsType == SectionFsType.Pfs0) switch (HashType)
{
case NcaHashType.Sha256:
Sha256Info = new Sha256Info(reader);
break;
case NcaHashType.Ivfc:
IvfcInfo = new IvfcHeader(reader);
break;
}
if (EncryptionType == NcaEncryptionType.AesCtrEx)
{
BktrInfo = new BktrPatchInfo();
reader.BaseStream.Position = start + 0x100;
BktrInfo.RelocationHeader = new BktrHeader(reader);
BktrInfo.EncryptionHeader = new BktrHeader(reader);
}
if (FormatType == NcaFormatType.Pfs0)
{ {
Type = SectionType.Pfs0; Type = SectionType.Pfs0;
Pfs = new PfsSuperblock(reader);
} }
else if (PartitionType == SectionPartitionType.Romfs && FsType == SectionFsType.Romfs) else if (FormatType == NcaFormatType.Romfs)
{ {
if (CryptType == SectionCryptType.BKTR) if (EncryptionType == NcaEncryptionType.AesCtrEx)
{ {
Type = SectionType.Bktr; Type = SectionType.Bktr;
Bktr = new BktrSuperblock(reader);
} }
else else
{ {
Type = SectionType.Romfs; Type = SectionType.Romfs;
Romfs = new RomfsSuperblock(reader);
} }
} }
reader.BaseStream.Position = start + 0x140;
Ctr = reader.ReadBytes(8).Reverse().ToArray(); Ctr = reader.ReadBytes(8).Reverse().ToArray();
reader.BaseStream.Position = start + 512; reader.BaseStream.Position = start + 512;
} }
} }
@ -156,10 +174,16 @@ namespace LibHac
} }
} }
public class BktrPatchInfo
{
public BktrHeader RelocationHeader;
public BktrHeader EncryptionHeader;
}
public class IvfcHeader public class IvfcHeader
{ {
public string Magic; public string Magic;
public uint Id; public int Version;
public uint MasterHashSize; public uint MasterHashSize;
public uint NumLevels; public uint NumLevels;
public IvfcLevelHeader[] LevelHeaders = new IvfcLevelHeader[6]; public IvfcLevelHeader[] LevelHeaders = new IvfcLevelHeader[6];
@ -169,7 +193,8 @@ namespace LibHac
public IvfcHeader(BinaryReader reader) public IvfcHeader(BinaryReader reader)
{ {
Magic = reader.ReadAscii(4); Magic = reader.ReadAscii(4);
Id = reader.ReadUInt32(); Version = reader.ReadInt16();
reader.BaseStream.Position += 2;
MasterHashSize = reader.ReadUInt32(); MasterHashSize = reader.ReadUInt32();
NumLevels = reader.ReadUInt32(); NumLevels = reader.ReadUInt32();
@ -187,24 +212,46 @@ namespace LibHac
{ {
public long LogicalOffset; public long LogicalOffset;
public long HashDataSize; public long HashDataSize;
public int BlockSize; public int BlockSizePower;
public uint Reserved; public uint Reserved;
public IvfcLevelHeader(BinaryReader reader) public IvfcLevelHeader(BinaryReader reader)
{ {
LogicalOffset = reader.ReadInt64(); LogicalOffset = reader.ReadInt64();
HashDataSize = reader.ReadInt64(); HashDataSize = reader.ReadInt64();
BlockSize = reader.ReadInt32(); BlockSizePower = reader.ReadInt32();
Reserved = reader.ReadUInt32(); Reserved = reader.ReadUInt32();
} }
} }
public class Sha256Info
{
public byte[] MasterHash;
public int BlockSize; // In bytes
public uint Always2;
public long HashTableOffset;
public long HashTableSize;
public long DataOffset;
public long DataSize;
public Sha256Info(BinaryReader reader)
{
MasterHash = reader.ReadBytes(0x20);
BlockSize = reader.ReadInt32();
Always2 = reader.ReadUInt32();
HashTableOffset = reader.ReadInt64();
HashTableSize = reader.ReadInt64();
DataOffset = reader.ReadInt64();
DataSize = reader.ReadInt64();
}
}
public class BktrHeader public class BktrHeader
{ {
public long Offset; public long Offset;
public long Size; public long Size;
public uint Magic; public uint Magic;
public uint Field14; public uint Version;
public uint NumEntries; public uint NumEntries;
public uint Field1C; public uint Field1C;
@ -213,7 +260,7 @@ namespace LibHac
Offset = reader.ReadInt64(); Offset = reader.ReadInt64();
Size = reader.ReadInt64(); Size = reader.ReadInt64();
Magic = reader.ReadUInt32(); Magic = reader.ReadUInt32();
Field14 = reader.ReadUInt32(); Version = reader.ReadUInt32();
NumEntries = reader.ReadUInt32(); NumEntries = reader.ReadUInt32();
Field1C = reader.ReadUInt32(); Field1C = reader.ReadUInt32();
} }
@ -281,21 +328,24 @@ namespace LibHac
Gamecard Gamecard
} }
public enum SectionCryptType public enum NcaEncryptionType
{ {
None = 1, Auto,
None,
XTS, XTS,
CTR, AesCtr,
BKTR AesCtrEx
} }
public enum SectionFsType public enum NcaHashType
{ {
Pfs0 = 2, Auto,
Romfs None,
Sha256,
Ivfc
} }
public enum SectionPartitionType public enum NcaFormatType
{ {
Romfs, Romfs,
Pfs0 Pfs0

View file

@ -71,7 +71,7 @@ namespace LibHac
public class PfsSuperblock public class PfsSuperblock
{ {
public byte[] MasterHash; /* SHA-256 hash of the hash table. */ public byte[] MasterHash; /* SHA-256 hash of the hash table. */
public uint BlockSize; /* In bytes. */ public int BlockSize; /* In bytes. */
public uint Always2; public uint Always2;
public long HashTableOffset; /* Normally zero. */ public long HashTableOffset; /* Normally zero. */
public long HashTableSize; public long HashTableSize;
@ -81,7 +81,7 @@ namespace LibHac
public PfsSuperblock(BinaryReader reader) public PfsSuperblock(BinaryReader reader)
{ {
MasterHash = reader.ReadBytes(0x20); MasterHash = reader.ReadBytes(0x20);
BlockSize = reader.ReadUInt32(); BlockSize = reader.ReadInt32();
Always2 = reader.ReadUInt32(); Always2 = reader.ReadUInt32();
HashTableOffset = reader.ReadInt64(); HashTableOffset = reader.ReadInt64();
HashTableSize = reader.ReadInt64(); HashTableSize = reader.ReadInt64();

View file

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -15,7 +14,7 @@ namespace LibHac
public List<RomfsFile> Files { get; } = new List<RomfsFile>(); public List<RomfsFile> Files { get; } = new List<RomfsFile>();
public RomfsDir RootDir { get; } public RomfsDir RootDir { get; }
private Dictionary<string, RomfsFile> FileDict { get; } public Dictionary<string, RomfsFile> FileDict { get; }
private SharedStreamSource StreamSource { get; } private SharedStreamSource StreamSource { get; }
public Romfs(Stream stream) public Romfs(Stream stream)
@ -40,7 +39,7 @@ namespace LibHac
{ {
var dir = new RomfsDir(reader) { Offset = position }; var dir = new RomfsDir(reader) { Offset = position };
Directories.Add(dir); Directories.Add(dir);
if (dir.ParentOffset == position) RootDir = dir; if (dir.ParentDirOffset == position) RootDir = dir;
position = (int)reader.BaseStream.Position; position = (int)reader.BaseStream.Position;
} }
} }
@ -57,7 +56,8 @@ namespace LibHac
} }
SetReferences(); SetReferences();
ResolveFilenames(); RomfsEntry.ResolveFilenames(Files);
RomfsEntry.ResolveFilenames(Directories);
FileDict = Files.ToDictionary(x => x.FullPath, x => x); FileDict = Files.ToDictionary(x => x.FullPath, x => x);
} }
@ -99,7 +99,7 @@ namespace LibHac
foreach (RomfsDir dir in Directories) foreach (RomfsDir dir in Directories)
{ {
if (dir.ParentOffset >= 0 && dir.ParentOffset != dir.Offset) dir.Parent = dirDict[dir.ParentOffset]; if (dir.ParentDirOffset >= 0 && dir.ParentDirOffset != dir.Offset) dir.ParentDir = dirDict[dir.ParentDirOffset];
if (dir.NextSiblingOffset >= 0) dir.NextSibling = dirDict[dir.NextSiblingOffset]; if (dir.NextSiblingOffset >= 0) dir.NextSibling = dirDict[dir.NextSiblingOffset];
if (dir.FirstChildOffset >= 0) dir.FirstChild = dirDict[dir.FirstChildOffset]; if (dir.FirstChildOffset >= 0) dir.FirstChild = dirDict[dir.FirstChildOffset];
if (dir.FirstFileOffset >= 0) dir.FirstFile = fileDict[dir.FirstFileOffset]; if (dir.FirstFileOffset >= 0) dir.FirstFile = fileDict[dir.FirstFileOffset];
@ -113,33 +113,6 @@ namespace LibHac
if (file.NextFileHashOffset >= 0) file.NextFileHash = fileDict[file.NextFileHashOffset]; if (file.NextFileHashOffset >= 0) file.NextFileHash = fileDict[file.NextFileHashOffset];
} }
} }
private void ResolveFilenames()
{
var list = new List<string>();
var sb = new StringBuilder();
string delimiter = "/";
foreach (RomfsFile file in Files)
{
list.Add(file.Name);
RomfsDir dir = file.ParentDir;
while (dir != null)
{
list.Add(delimiter);
list.Add(dir.Name);
dir = dir.Parent;
}
for (int i = list.Count - 1; i >= 0; i--)
{
sb.Append(list[i]);
}
file.FullPath = sb.ToString();
list.Clear();
sb.Clear();
}
}
} }
public class RomfsHeader public class RomfsHeader
@ -170,66 +143,7 @@ namespace LibHac
} }
} }
[DebuggerDisplay("{" + nameof(Name) + "}")]
public class RomfsDir
{
public int Offset { get; set; }
public int ParentOffset { get; }
public int NextSiblingOffset { get; }
public int FirstChildOffset { get; }
public int FirstFileOffset { get; }
public int NextDirHashOffset { get; }
public int NameLength { get; }
public string Name { get; }
public RomfsDir Parent { get; internal set; }
public RomfsDir NextSibling { get; internal set; }
public RomfsDir FirstChild { get; internal set; }
public RomfsFile FirstFile { get; internal set; }
public RomfsDir NextDirHash { get; internal set; }
public RomfsDir(BinaryReader reader)
{
ParentOffset = reader.ReadInt32();
NextSiblingOffset = reader.ReadInt32();
FirstChildOffset = reader.ReadInt32();
FirstFileOffset = reader.ReadInt32();
NextDirHashOffset = reader.ReadInt32();
NameLength = reader.ReadInt32();
Name = reader.ReadUtf8(NameLength);
reader.BaseStream.Position = Util.GetNextMultiple(reader.BaseStream.Position, 4);
}
}
[DebuggerDisplay("{" + nameof(Name) + "}")]
public class RomfsFile
{
public int Offset { get; set; }
public int ParentDirOffset { get; }
public int NextSiblingOffset { get; }
public long DataOffset { get; }
public long DataLength { get; }
public int NextFileHashOffset { get; }
public int NameLength { get; }
public string Name { get; }
public RomfsDir ParentDir { get; internal set; }
public RomfsFile NextSibling { get; internal set; }
public RomfsFile NextFileHash { get; internal set; }
public string FullPath { get; set; }
public RomfsFile(BinaryReader reader)
{
ParentDirOffset = reader.ReadInt32();
NextSiblingOffset = reader.ReadInt32();
DataOffset = reader.ReadInt64();
DataLength = reader.ReadInt64();
NextFileHashOffset = reader.ReadInt32();
NameLength = reader.ReadInt32();
Name = reader.ReadUtf8(NameLength);
reader.BaseStream.Position = Util.GetNextMultiple(reader.BaseStream.Position, 4);
}
}
public class IvfcLevel public class IvfcLevel
{ {

95
LibHac/RomfsEntry.cs Normal file
View file

@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace LibHac
{
public abstract class RomfsEntry
{
public int Offset { get; set; }
public int ParentDirOffset { get; protected set; }
public int NameLength { get; protected set; }
public string Name { get; protected set; }
public RomfsDir ParentDir { get; internal set; }
public string FullPath { get; private set; }
internal static void ResolveFilenames(IEnumerable<RomfsEntry> entries)
{
var list = new List<string>();
var sb = new StringBuilder();
const string delimiter = "/";
foreach (RomfsEntry file in entries)
{
list.Add(file.Name);
RomfsDir dir = file.ParentDir;
while (dir != null)
{
list.Add(delimiter);
list.Add(dir.Name);
dir = dir.ParentDir;
}
for (int i = list.Count - 1; i >= 0; i--)
{
sb.Append(list[i]);
}
file.FullPath = sb.ToString();
list.Clear();
sb.Clear();
}
}
}
[DebuggerDisplay("{" + nameof(Name) + "}")]
public class RomfsDir : RomfsEntry
{
public int NextSiblingOffset { get; }
public int FirstChildOffset { get; }
public int FirstFileOffset { get; }
public int NextDirHashOffset { get; }
public RomfsDir NextSibling { get; internal set; }
public RomfsDir FirstChild { get; internal set; }
public RomfsFile FirstFile { get; internal set; }
public RomfsDir NextDirHash { get; internal set; }
public RomfsDir(BinaryReader reader)
{
ParentDirOffset = reader.ReadInt32();
NextSiblingOffset = reader.ReadInt32();
FirstChildOffset = reader.ReadInt32();
FirstFileOffset = reader.ReadInt32();
NextDirHashOffset = reader.ReadInt32();
NameLength = reader.ReadInt32();
Name = reader.ReadUtf8(NameLength);
reader.BaseStream.Position = Util.GetNextMultiple(reader.BaseStream.Position, 4);
}
}
[DebuggerDisplay("{" + nameof(Name) + "}")]
public class RomfsFile : RomfsEntry
{
public int NextSiblingOffset { get; }
public long DataOffset { get; }
public long DataLength { get; }
public int NextFileHashOffset { get; }
public RomfsFile NextSibling { get; internal set; }
public RomfsFile NextFileHash { get; internal set; }
public RomfsFile(BinaryReader reader)
{
ParentDirOffset = reader.ReadInt32();
NextSiblingOffset = reader.ReadInt32();
DataOffset = reader.ReadInt64();
DataLength = reader.ReadInt64();
NextFileHashOffset = reader.ReadInt32();
NameLength = reader.ReadInt32();
Name = reader.ReadUtf8(NameLength);
reader.BaseStream.Position = Util.GetNextMultiple(reader.BaseStream.Position, 4);
}
}
}

View file

@ -130,7 +130,7 @@ namespace LibHac.Savefile
initInfo[0] = new IntegrityVerificationInfo initInfo[0] = new IntegrityVerificationInfo
{ {
Data = new MemoryStream(Header.MasterHashA), Data = new MemoryStream(Header.MasterHashA),
BlockSizePower = 0, BlockSize = 0,
Type = IntegrityStreamType.Save Type = IntegrityStreamType.Save
}; };
@ -145,7 +145,7 @@ namespace LibHac.Savefile
initInfo[i] = new IntegrityVerificationInfo initInfo[i] = new IntegrityVerificationInfo
{ {
Data = data, Data = data,
BlockSizePower = level.BlockSize, BlockSize = 1 << level.BlockSizePower,
Salt = new HMACSHA256(Encoding.ASCII.GetBytes(SaltSources[i - 1])).ComputeHash(ivfc.SaltSource), Salt = new HMACSHA256(Encoding.ASCII.GetBytes(SaltSources[i - 1])).ComputeHash(ivfc.SaltSource),
Type = IntegrityStreamType.Save Type = IntegrityStreamType.Save
}; };

View file

@ -1,15 +1,20 @@
using System.IO; using System;
using System.IO;
namespace LibHac.Streams namespace LibHac.Streams
{ {
public class SharedStreamSource public class SharedStreamSource : IDisposable
{ {
private Stream BaseStream { get; } private Stream BaseStream { get; }
private object Locker { get; } = new object(); private object Locker { get; } = new object();
private bool KeepOpen { get; }
public SharedStreamSource(Stream baseStream) public SharedStreamSource(Stream baseStream) : this(baseStream, true) { }
public SharedStreamSource(Stream baseStream, bool keepOpen)
{ {
BaseStream = baseStream; BaseStream = baseStream;
KeepOpen = keepOpen;
} }
public SharedStream CreateStream() public SharedStream CreateStream()
@ -59,5 +64,13 @@ namespace LibHac.Streams
public bool CanSeek => BaseStream.CanSeek; public bool CanSeek => BaseStream.CanSeek;
public bool CanWrite => BaseStream.CanWrite; public bool CanWrite => BaseStream.CanWrite;
public long Length => BaseStream.Length; public long Length => BaseStream.Length;
public void Dispose()
{
if (KeepOpen)
{
BaseStream?.Dispose();
}
}
} }
} }

View file

@ -178,34 +178,43 @@ namespace hactoolnet
void PrintPfs0(NcaSection sect) void PrintPfs0(NcaSection sect)
{ {
PfsSuperblock sBlock = sect.Pfs0.Superblock; Sha256Info hashInfo = sect.Header.Sha256Info;
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, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", hashInfo.MasterHash);
PrintItem(sb, colLen, " Size:", $"0x{sBlock.HashTableSize:x12}"); // todo sb.AppendLine($" Hash Table{sect.Pfs0.Validity.GetValidityString()}:");
PrintItem(sb, colLen, " Block Size:", $"0x{sBlock.BlockSize:x}"); sb.AppendLine($" Hash Table:");
PrintItem(sb, colLen, " PFS0 Offset:", $"0x{sBlock.Pfs0Offset:x12}");
PrintItem(sb, colLen, " PFS0 Size:", $"0x{sBlock.Pfs0Size:x12}"); 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}");
} }
void PrintRomfs(NcaSection sect) void PrintRomfs(NcaSection sect)
{ {
RomfsSuperblock sBlock = sect.Romfs.Superblock; IvfcHeader ivfcInfo = sect.Header.IvfcInfo;
IvfcLevel[] levels = sect.Romfs.IvfcLevels;
PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", sBlock.IvfcHeader.MasterHash); PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", ivfcInfo.MasterHash);
PrintItem(sb, colLen, " Magic:", sBlock.IvfcHeader.Magic); PrintItem(sb, colLen, " Magic:", ivfcInfo.Magic);
PrintItem(sb, colLen, " ID:", $"{sBlock.IvfcHeader.Id:x8}"); PrintItem(sb, colLen, " Version:", $"{ivfcInfo.Version:x8}");
for (int i = 0; i < Romfs.IvfcMaxLevel; i++) for (int i = 0; i < Romfs.IvfcMaxLevel; i++)
{ {
IvfcLevel level = levels[i]; IvfcLevelHeader level = ivfcInfo.LevelHeaders[i];
sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:"); long hashOffset = 0;
PrintItem(sb, colLen, " Data Offset:", $"0x{level.DataOffset:x12}");
PrintItem(sb, colLen, " Data Size:", $"0x{level.DataSize:x12}"); if (i != 0)
PrintItem(sb, colLen, " Hash Offset:", $"0x{level.HashOffset:x12}"); {
PrintItem(sb, colLen, " Hash BlockSize:", $"0x{level.HashBlockSize:x8}"); hashOffset = ivfcInfo.LevelHeaders[i - 1].LogicalOffset;
}
// todo sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:");
sb.AppendLine($" Level {i}:");
PrintItem(sb, colLen, " Data Offset:", $"0x{level.LogicalOffset:x12}");
PrintItem(sb, colLen, " Data Size:", $"0x{level.HashDataSize:x12}");
PrintItem(sb, colLen, " Hash Offset:", $"0x{hashOffset:x12}");
PrintItem(sb, colLen, " Hash BlockSize:", $"0x{1 << level.BlockSizePower:x8}");
} }
} }
} }

View file

@ -170,7 +170,7 @@ namespace hactoolnet
foreach (NcaSection sect in nca.Sections.Where(x => x != null)) foreach (NcaSection sect in nca.Sections.Where(x => x != null))
{ {
Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.CryptType} {sect.SuperblockHashValidity}"); Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.EncryptionType} {sect.SuperblockHashValidity}");
} }
} }

View file

@ -100,7 +100,7 @@ namespace hactoolnet
ctx.Keyset.SetSdSeed(ctx.Options.SdSeed.ToBytes()); ctx.Keyset.SetSdSeed(ctx.Options.SdSeed.ToBytes());
} }
if (ctx.Options.OutDir != null) if (ctx.Options.InFileType == FileType.Keygen && ctx.Options.OutDir != null)
{ {
string dir = ctx.Options.OutDir; string dir = ctx.Options.OutDir;
Directory.CreateDirectory(dir); Directory.CreateDirectory(dir);