Merge remote-tracking branch 'upstream/master'

This commit is contained in:
jonnysp 2018-10-13 05:49:24 +02:00
commit ffd1af4c8e
24 changed files with 556 additions and 277 deletions

View file

@ -19,7 +19,7 @@ namespace LibHac
public Bktr(Stream patchRomfs, Stream baseRomfs, NcaSection section)
{
if (section.Type != SectionType.Bktr) throw new ArgumentException("Section is not of type BKTR");
if (section.Header.EncryptionType != NcaEncryptionType.AesCtrEx) throw new ArgumentException("Section is not of type BKTR");
Patch = patchRomfs ?? throw new NullReferenceException($"{nameof(patchRomfs)} cannot be null");
Base = baseRomfs ?? throw new NullReferenceException($"{nameof(baseRomfs)} cannot be null");

View file

@ -87,6 +87,10 @@ namespace LibHac
CurrentEntry = CurrentEntry.Next;
UpdateCounterSubsection(CurrentEntry.Counter);
}
else if (bytesRead == 0)
{
break;
}
}
return totalBytesRead;

View file

@ -8,25 +8,78 @@ namespace LibHac
{
public Stream[] Levels { get; }
public Stream DataLevel { get; }
public bool EnableIntegrityChecks { get; }
public IntegrityCheckLevel IntegrityCheckLevel { get; }
public HierarchicalIntegrityVerificationStream(IntegrityVerificationInfo[] levelInfo, bool enableIntegrityChecks)
/// <summary>
/// An array of the hash statuses of every block in each level.
/// </summary>
public Validity[][] LevelValidities { get; }
private IntegrityVerificationStream[] IntegrityStreams { get; }
public HierarchicalIntegrityVerificationStream(IntegrityVerificationInfo[] levelInfo, IntegrityCheckLevel integrityCheckLevel)
{
Levels = new Stream[levelInfo.Length];
EnableIntegrityChecks = enableIntegrityChecks;
IntegrityCheckLevel = integrityCheckLevel;
LevelValidities = new Validity[levelInfo.Length - 1][];
IntegrityStreams = new IntegrityVerificationStream[levelInfo.Length - 1];
Levels[0] = levelInfo[0].Data;
for (int i = 1; i < Levels.Length; i++)
{
var levelData = new IntegrityVerificationStream(levelInfo[i], Levels[i - 1], enableIntegrityChecks);
var levelData = new IntegrityVerificationStream(levelInfo[i], Levels[i - 1], integrityCheckLevel);
Levels[i] = new RandomAccessSectorStream(levelData);
LevelValidities[i - 1] = levelData.BlockValidities;
IntegrityStreams[i - 1] = levelData;
}
DataLevel = Levels[Levels.Length - 1];
}
/// <summary>
/// Checks the hashes of any unchecked blocks and returns the <see cref="Validity"/> of the hash level.
/// </summary>
/// <param name="level">The level of hierarchical hashes to check.</param>
/// <param name="returnOnError">If <see langword="true"/>, return as soon as an invalid block is found.</param>
/// <param name="logger">An optional <see cref="IProgressReport"/> for reporting progress.</param>
/// <returns>The <see cref="Validity"/> of the data of the specified hash level.</returns>
public Validity ValidateLevel(int level, bool returnOnError, IProgressReport logger = null)
{
Validity[] validities = LevelValidities[level];
IntegrityVerificationStream levelStream = IntegrityStreams[level];
// The original position of the stream must be restored when we're done validating
long initialPosition = levelStream.Position;
var buffer = new byte[levelStream.SectorSize];
var result = Validity.Valid;
logger?.SetTotal(levelStream.SectorCount);
for (int i = 0; i < levelStream.SectorCount; i++)
{
if (validities[i] == Validity.Unchecked)
{
levelStream.Position = (long)levelStream.SectorSize * i;
levelStream.Read(buffer, 0, buffer.Length, IntegrityCheckLevel.IgnoreOnInvalid);
}
if (validities[i] == Validity.Invalid)
{
result = Validity.Invalid;
if (returnOnError) break;
}
logger?.ReportAdd(1);
}
logger?.SetTotal(0);
levelStream.Position = initialPosition;
return result;
}
public override void Flush()
{
throw new NotImplementedException();

View file

@ -10,7 +10,8 @@ namespace LibHac
private const int DigestSize = 0x20;
private Stream HashStream { get; }
public bool EnableIntegrityChecks { get; }
public IntegrityCheckLevel IntegrityCheckLevel { get; }
public Validity[] BlockValidities { get; }
private byte[] Salt { get; }
private IntegrityStreamType Type { get; }
@ -18,13 +19,15 @@ namespace LibHac
private readonly byte[] _hashBuffer = new byte[DigestSize];
private readonly SHA256 _hash = SHA256.Create();
public IntegrityVerificationStream(IntegrityVerificationInfo info, Stream hashStream, bool enableIntegrityChecks)
public IntegrityVerificationStream(IntegrityVerificationInfo info, Stream hashStream, IntegrityCheckLevel integrityCheckLevel)
: base(info.Data, info.BlockSize)
{
HashStream = hashStream;
EnableIntegrityChecks = enableIntegrityChecks;
IntegrityCheckLevel = integrityCheckLevel;
Salt = info.Salt;
Type = info.Type;
BlockValidities = new Validity[SectorCount];
}
public override void Flush()
@ -55,12 +58,16 @@ namespace LibHac
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
public override int Read(byte[] buffer, int offset, int count) =>
Read(buffer, offset, count, IntegrityCheckLevel);
public int Read(byte[] buffer, int offset, int count, IntegrityCheckLevel integrityCheckLevel)
{
HashStream.Position = CurrentSector * DigestSize;
long blockNum = CurrentSector;
HashStream.Position = blockNum * DigestSize;
HashStream.Read(_hashBuffer, 0, DigestSize);
int bytesRead = base.Read(buffer, 0, count);
int bytesRead = base.Read(buffer, offset, count);
int bytesToHash = SectorSize;
if (bytesRead == 0) return 0;
@ -68,14 +75,14 @@ namespace LibHac
// If a hash is zero the data for the entire block is zero
if (Type == IntegrityStreamType.Save && _hashBuffer.IsEmpty())
{
Array.Clear(buffer, 0, SectorSize);
Array.Clear(buffer, offset, SectorSize);
return bytesRead;
}
if (bytesRead < SectorSize)
{
// Pad out unused portion of block
Array.Clear(buffer, bytesRead, SectorSize - bytesRead);
Array.Clear(buffer, offset + bytesRead, SectorSize - bytesRead);
// Partition FS hashes don't pad out an incomplete block
if (Type == IntegrityStreamType.PartitionFs)
@ -83,8 +90,15 @@ namespace LibHac
bytesToHash = bytesRead;
}
}
if (BlockValidities[blockNum] == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
{
throw new InvalidDataException("Hash error!");
}
if (!EnableIntegrityChecks) return bytesRead;
if (integrityCheckLevel == IntegrityCheckLevel.None) return bytesRead;
if (BlockValidities[blockNum] != Validity.Unchecked) return bytesRead;
_hash.Initialize();
@ -93,7 +107,7 @@ namespace LibHac
_hash.TransformBlock(Salt, 0, Salt.Length, null, 0);
}
_hash.TransformBlock(buffer, 0, bytesToHash, null, 0);
_hash.TransformBlock(buffer, offset, bytesToHash, null, 0);
_hash.TransformFinalBlock(buffer, 0, 0);
byte[] hash = _hash.Hash;
@ -104,7 +118,10 @@ namespace LibHac
hash[0x1F] |= 0x80;
}
if (!Util.ArraysEqual(_hashBuffer, hash))
Validity validity = Util.ArraysEqual(_hashBuffer, hash) ? Validity.Valid : Validity.Invalid;
BlockValidities[blockNum] = validity;
if (validity == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
{
throw new InvalidDataException("Hash error!");
}
@ -139,4 +156,23 @@ namespace LibHac
RomFs,
PartitionFs
}
/// <summary>
/// Represents the level of integrity checks to be performed.
/// </summary>
public enum IntegrityCheckLevel
{
/// <summary>
/// No integrity checks will be performed.
/// </summary>
None,
/// <summary>
/// Invalid blocks will be marked as invalid when read, and will not cause an error.
/// </summary>
IgnoreOnInvalid,
/// <summary>
/// An <see cref="InvalidDataException"/> will be thrown if an integrity check fails.
/// </summary>
ErrorOnInvalid
}
}

View file

@ -246,7 +246,7 @@ namespace LibHac
Crypto.DecryptEcb(headerKek, HeaderKeySource, HeaderKey, 0x20);
}
private void DeriveSdCardKeys()
public void DeriveSdCardKeys()
{
var sdKek = new byte[0x10];
Crypto.GenerateKek(MasterKeys[0], SdCardKekSource, sdKek, AesKekGenerationSource, AesKeyGenerationSource);
@ -264,6 +264,8 @@ namespace LibHac
Crypto.DecryptEcb(sdKek, SdCardKeySourcesSpecific[k], SdCardKeys[k], 0x20);
}
}
internal static readonly string[] KakNames = {"application", "ocean", "system"};
}
public static class ExternalKeys
@ -396,7 +398,7 @@ namespace LibHac
public static string PrintKeys(Keyset keyset, Dictionary<string, KeyValue> dict)
{
if(dict.Count == 0) return string.Empty;
if (dict.Count == 0) return string.Empty;
var sb = new StringBuilder();
int maxNameLength = dict.Values.Max(x => x.Name.Length);

View file

@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Linq;
using LibHac.Streams;
using LibHac.XTSSharp;
@ -20,6 +19,9 @@ namespace LibHac
private bool KeepOpen { get; }
private Nca BaseNca { get; set; }
private bool IsMissingTitleKey { get; set; }
private string MissingKeyName { get; set; }
public NcaSection[] Sections { get; } = new NcaSection[4];
public Nca(Keyset keyset, Stream stream, bool keepOpen)
@ -38,19 +40,20 @@ namespace LibHac
{
DecryptKeyArea(keyset);
}
else if (keyset.TitleKeys.TryGetValue(Header.RightsId, out byte[] titleKey))
{
if (keyset.Titlekeks[CryptoType].IsEmpty())
{
MissingKeyName = $"titlekek_{CryptoType:x2}";
}
TitleKey = titleKey;
Crypto.DecryptEcb(keyset.Titlekeks[CryptoType], titleKey, TitleKeyDec, 0x10);
DecryptedKeys[2] = TitleKeyDec;
}
else
{
if (keyset.TitleKeys.TryGetValue(Header.RightsId, out byte[] titleKey))
{
TitleKey = titleKey;
Crypto.DecryptEcb(keyset.Titlekeks[CryptoType], titleKey, TitleKeyDec, 0x10);
DecryptedKeys[2] = TitleKeyDec;
}
else
{
// todo enable key check when opening a section
// throw new MissingKeyException("A required key is missing.", $"{Header.RightsId.ToHexString()}", KeyType.Title);
}
IsMissingTitleKey = true;
}
for (int i = 0; i < 4; i++)
@ -58,36 +61,57 @@ namespace LibHac
NcaSection section = ParseSection(i);
if (section == null) continue;
Sections[i] = section;
ValidateSuperblockHash(i);
}
foreach (NcaSection pfsSection in Sections.Where(x => x != null && x.Type == SectionType.Pfs0))
{
Stream sectionStream = OpenSection(pfsSection.SectionNum, false, false);
if (sectionStream == null) continue;
var pfs = new Pfs(sectionStream);
if (!pfs.FileExists("main.npdm")) continue;
pfsSection.IsExefs = true;
}
}
/// <summary>
/// Opens a <see cref="Stream"/> of the underlying NCA file.
/// </summary>
/// <returns>A <see cref="Stream"/> that provides access to the entire raw NCA file.</returns>
public Stream GetStream()
{
return StreamSource.CreateStream();
}
public bool CanOpenSection(int index)
{
if (index < 0 || index > 3) throw new ArgumentOutOfRangeException(nameof(index));
NcaSection sect = Sections[index];
if (sect == null) return false;
return sect.Header.EncryptionType == NcaEncryptionType.None || !IsMissingTitleKey && string.IsNullOrWhiteSpace(MissingKeyName);
}
private Stream OpenRawSection(int index)
{
NcaSection sect = Sections[index];
if (sect == null) throw new ArgumentOutOfRangeException(nameof(index));
if (index < 0 || index > 3) throw new ArgumentOutOfRangeException(nameof(index));
//if (sect.SuperblockHashValidity == Validity.Invalid) return null;
NcaSection sect = Sections[index];
if (sect == null) return null;
if (sect.Header.EncryptionType != NcaEncryptionType.None)
{
if (IsMissingTitleKey)
{
throw new MissingKeyException("Unable to decrypt NCA section.", Header.RightsId.ToHexString(), KeyType.Title);
}
if (!string.IsNullOrWhiteSpace(MissingKeyName))
{
throw new MissingKeyException("Unable to decrypt NCA section.", MissingKeyName, KeyType.Common);
}
}
long offset = sect.Offset;
long size = sect.Size;
if (!Util.IsSubRange(offset, size, StreamSource.Length))
{
throw new InvalidDataException(
$"Section offset (0x{offset:x}) and length (0x{size:x}) fall outside the total NCA length (0x{StreamSource.Length:x}).");
}
Stream rawStream = StreamSource.CreateStream(offset, size);
switch (sect.Header.EncryptionType)
@ -104,10 +128,8 @@ namespace LibHac
false);
if (BaseNca == null) return rawStream;
NcaSection baseSect = BaseNca.Sections.FirstOrDefault(x => x.Type == SectionType.Romfs);
if (baseSect == null) throw new InvalidDataException("Base NCA has no RomFS section");
Stream baseStream = BaseNca.OpenSection(ProgramPartitionType.Data, true, IntegrityCheckLevel.None) ?? Stream.Null;
Stream baseStream = BaseNca.OpenSection(baseSect.SectionNum, true, false);
return new Bktr(rawStream, baseStream, sect);
default:
@ -115,27 +137,65 @@ namespace LibHac
}
}
public Stream OpenSection(int index, bool raw, bool enableIntegrityChecks)
/// <summary>
/// Opens one of the sections in the current <see cref="Nca"/>.
/// </summary>
/// <param name="index">The index of the NCA section to open. Valid indexes are 0-3.</param>
/// <param name="raw"><see langword="true"/> to open the raw section with hash metadata.</param>
/// <param name="integrityCheckLevel">The level of integrity checks to be performed when reading the section.
/// Always <see cref="IntegrityCheckLevel.None"/> if <paramref name="raw"/> is <see langword="false"/>.</param>
/// <returns>A <see cref="Stream"/> that provides access to the specified section. <see langword="null"/> if the section does not exist.</returns>
/// <exception cref="ArgumentOutOfRangeException">The specified <paramref name="index"/> is outside the valid range.</exception>
public Stream OpenSection(int index, bool raw, IntegrityCheckLevel integrityCheckLevel)
{
Stream rawStream = OpenRawSection(index);
NcaSection sect = Sections[index];
if (raw || rawStream == null) return rawStream;
switch (sect.Header.Type)
NcaSection sect = Sections[index];
NcaFsHeader header = sect.Header;
// If it's a patch section without a base, return the raw section because it has no hash data
if (header.EncryptionType == NcaEncryptionType.AesCtrEx && BaseNca == null) return rawStream;
switch (header.HashType)
{
case SectionType.Pfs0:
return InitIvfcForPartitionfs(sect.Header.Sha256Info, new SharedStreamSource(rawStream), enableIntegrityChecks);
case SectionType.Romfs:
case SectionType.Bktr:
return InitIvfcForRomfs(sect.Header.IvfcInfo, new SharedStreamSource(rawStream), enableIntegrityChecks);
case NcaHashType.Sha256:
return InitIvfcForPartitionfs(header.Sha256Info, new SharedStreamSource(rawStream), integrityCheckLevel);
case NcaHashType.Ivfc:
return InitIvfcForRomfs(header.IvfcInfo, new SharedStreamSource(rawStream), integrityCheckLevel);
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>
/// Opens one of the sections in the current <see cref="Nca"/> as a <see cref="HierarchicalIntegrityVerificationStream"/>
/// Only works with sections that have a <see cref="NcaFsHeader.HashType"/> of <see cref="NcaHashType.Ivfc"/> or <see cref="NcaHashType.Sha256"/>.
/// </summary>
/// <param name="index">The index of the NCA section to open. Valid indexes are 0-3.</param>
/// <param name="integrityCheckLevel">The level of integrity checks to be performed when reading the section.</param>
/// <returns>A <see cref="Stream"/> that provides access to the specified section. <see langword="null"/> if the section does not exist,
/// or is has no hash metadata.</returns>
/// <exception cref="ArgumentOutOfRangeException">The specified <paramref name="index"/> is outside the valid range.</exception>
public HierarchicalIntegrityVerificationStream OpenHashedSection(int index, IntegrityCheckLevel integrityCheckLevel) =>
OpenSection(index, false, integrityCheckLevel) as HierarchicalIntegrityVerificationStream;
/// <summary>
/// Opens one of the sections in the current <see cref="Nca"/>. For use with <see cref="ContentType.Program"/> type NCAs.
/// </summary>
/// <param name="type">The type of section to open.</param>
/// <param name="raw"><see langword="true"/> to open the raw section with hash metadata.</param>
/// <param name="integrityCheckLevel">The level of integrity checks to be performed when reading the section.
/// Always <see cref="IntegrityCheckLevel.None"/> if <paramref name="raw"/> is <see langword="false"/>.</param>
/// <returns>A <see cref="Stream"/> that provides access to the specified section. <see langword="null"/> if the section does not exist.</returns>
/// <exception cref="ArgumentOutOfRangeException">The specified <paramref name="type"/> is outside the valid range.</exception>
public Stream OpenSection(ProgramPartitionType type, bool raw, IntegrityCheckLevel integrityCheckLevel) =>
OpenSection((int)type, raw, integrityCheckLevel);
private static HierarchicalIntegrityVerificationStream InitIvfcForRomfs(IvfcHeader ivfc,
SharedStreamSource romfsStreamSource, bool enableIntegrityChecks)
SharedStreamSource romfsStreamSource, IntegrityCheckLevel integrityCheckLevel)
{
var initInfo = new IntegrityVerificationInfo[ivfc.NumLevels];
@ -159,11 +219,11 @@ namespace LibHac
};
}
return new HierarchicalIntegrityVerificationStream(initInfo, enableIntegrityChecks);
return new HierarchicalIntegrityVerificationStream(initInfo, integrityCheckLevel);
}
private static Stream InitIvfcForPartitionfs(Sha256Info sb,
SharedStreamSource pfsStreamSource, bool enableIntegrityChecks)
SharedStreamSource pfsStreamSource, IntegrityCheckLevel integrityCheckLevel)
{
SharedStream hashStream = pfsStreamSource.CreateStream(sb.HashTableOffset, sb.HashTableSize);
SharedStream dataStream = pfsStreamSource.CreateStream(sb.DataOffset, sb.DataSize);
@ -192,13 +252,34 @@ namespace LibHac
Type = IntegrityStreamType.PartitionFs
};
return new HierarchicalIntegrityVerificationStream(initInfo, enableIntegrityChecks);
return new HierarchicalIntegrityVerificationStream(initInfo, integrityCheckLevel);
}
/// <summary>
/// Sets a base <see cref="Nca"/> to use when reading patches.
/// </summary>
/// <param name="baseNca">The base <see cref="Nca"/></param>
public void SetBaseNca(Nca baseNca) => BaseNca = baseNca;
/// <summary>
/// Validates the master hash and store the result in <see cref="NcaSection.MasterHashValidity"/> for each <see cref="NcaSection"/>.
/// </summary>
public void ValidateMasterHashes()
{
for (int i = 0; i < 4; i++)
{
if (Sections[i] == null) continue;
ValidateMasterHash(i);
}
}
private void DecryptHeader(Keyset keyset, Stream stream)
{
if (keyset.HeaderKey.IsEmpty())
{
throw new MissingKeyException("Unable to decrypt NCA header.", "header_key", KeyType.Common);
}
var headerBytes = new byte[0xC00];
Xts xts = XtsAes128.Create(keyset.HeaderKey);
using (var headerDec = new RandomAccessSectorStream(new XtsSectorStream(stream, xts, 0x200)))
@ -213,6 +294,12 @@ namespace LibHac
private void DecryptKeyArea(Keyset keyset)
{
if (keyset.KeyAreaKeys[CryptoType][Header.KaekInd].IsEmpty())
{
MissingKeyName = $"key_area_key_{Keyset.KakNames[Header.KaekInd]}_{CryptoType:x2}";
return;
}
for (int i = 0; i < 4; i++)
{
Crypto.DecryptEcb(keyset.KeyAreaKeys[CryptoType][Header.KaekInd], Header.EncryptedKeys[i],
@ -239,6 +326,10 @@ namespace LibHac
private void CheckBktrKey(NcaSection sect)
{
// The encryption subsection table in the bktr partition contains the length of the entire partition.
// The encryption table is always located immediately following the partition data
// Decrypt this value and compare it to the encryption table offset found in the NCA header
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)))
{
@ -248,75 +339,51 @@ namespace LibHac
if (size != offset)
{
sect.SuperblockHashValidity = Validity.Invalid;
sect.MasterHashValidity = Validity.Invalid;
}
}
}
private void ValidateSuperblockHash(int index)
private void ValidateMasterHash(int index)
{
if (Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index));
NcaSection sect = Sections[index];
byte[] expected = null;
if (!CanOpenSection(index))
{
sect.MasterHashValidity = Validity.MissingKey;
return;
}
byte[] expected = sect.GetMasterHash();
long offset = 0;
long size = 0;
switch (sect.Type)
switch (sect.Header.HashType)
{
case SectionType.Invalid:
case NcaHashType.Sha256:
offset = sect.Header.Sha256Info.HashTableOffset;
size = sect.Header.Sha256Info.HashTableSize;
break;
case SectionType.Pfs0:
Sha256Info pfs0 = sect.Header.Sha256Info;
expected = pfs0.MasterHash;
offset = pfs0.HashTableOffset;
size = pfs0.HashTableSize;
break;
case SectionType.Romfs:
IvfcHeader ivfc = sect.Header.IvfcInfo;
expected = ivfc.MasterHash;
offset = ivfc.LevelHeaders[0].LogicalOffset;
size = 1 << ivfc.LevelHeaders[0].BlockSizePower;
break;
case SectionType.Bktr:
case NcaHashType.Ivfc when sect.Header.EncryptionType == NcaEncryptionType.AesCtrEx:
CheckBktrKey(sect);
return;
case NcaHashType.Ivfc:
offset = sect.Header.IvfcInfo.LevelHeaders[0].LogicalOffset;
size = 1 << sect.Header.IvfcInfo.LevelHeaders[0].BlockSizePower;
break;
}
Stream stream = OpenSection(index, true, false);
if (stream == null) return;
if (expected == null) return;
Stream stream = OpenSection(index, true, IntegrityCheckLevel.None);
var hashTable = new byte[size];
stream.Position = offset;
stream.Read(hashTable, 0, hashTable.Length);
sect.SuperblockHashValidity = Crypto.CheckMemoryHashTable(hashTable, expected, 0, hashTable.Length);
// todo if (sect.Type == SectionType.Romfs) sect.Romfs.IvfcLevels[0].HashValidity = sect.SuperblockHashValidity;
sect.MasterHashValidity = Crypto.CheckMemoryHashTable(hashTable, expected, 0, hashTable.Length);
if (sect.Header.HashType == NcaHashType.Ivfc) sect.Header.IvfcInfo.LevelHeaders[0].HashValidity = sect.MasterHashValidity;
}
public void VerifySection(int index, IProgressReport logger = null)
{
if (Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index));
NcaSection sect = Sections[index];
Stream stream = OpenSection(index, true, false);
logger?.LogMessage($"Verifying section {index}...");
switch (sect.Type)
{
case SectionType.Invalid:
break;
case SectionType.Pfs0:
// todo VerifyPfs0(stream, sect.Pfs0, logger);
break;
case SectionType.Romfs:
// todo VerifyIvfc(stream, sect.Romfs.IvfcLevels, logger);
break;
case SectionType.Bktr:
break;
}
}
public void Dispose()
{
if (!KeepOpen)
@ -333,19 +400,34 @@ namespace LibHac
public int SectionNum { get; set; }
public long Offset { get; set; }
public long Size { get; set; }
public Validity SuperblockHashValidity { get; set; }
public Validity MasterHashValidity { get; set; }
public bool IsExefs { get; internal set; }
public byte[] GetMasterHash()
{
var hash = new byte[Crypto.Sha256DigestSize];
switch (Header.HashType)
{
case NcaHashType.Sha256:
Array.Copy(Header.Sha256Info.MasterHash, hash, Crypto.Sha256DigestSize);
break;
case NcaHashType.Ivfc:
Array.Copy(Header.IvfcInfo.MasterHash, hash, Crypto.Sha256DigestSize);
break;
}
return hash;
}
}
public static class NcaExtensions
{
public static void ExportSection(this Nca nca, int index, string filename, bool raw = false, bool verify = false, IProgressReport logger = null)
public static void ExportSection(this Nca nca, int index, string filename, bool raw = false, IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null)
{
if (index < 0 || index > 3) throw new IndexOutOfRangeException();
if (nca.Sections[index] == null) return;
Stream section = nca.OpenSection(index, raw, verify);
Stream section = nca.OpenSection(index, raw, integrityCheckLevel);
string dir = Path.GetDirectoryName(filename);
if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir);
@ -355,13 +437,13 @@ namespace LibHac
}
}
public static void ExtractSection(this Nca nca, int index, string outputDir, bool verify = false, IProgressReport logger = null)
public static void ExtractSection(this Nca nca, int index, string outputDir, IntegrityCheckLevel integrityCheckLevel = IntegrityCheckLevel.None, IProgressReport logger = null)
{
if (index < 0 || index > 3) throw new IndexOutOfRangeException();
if (nca.Sections[index] == null) return;
NcaSection section = nca.Sections[index];
Stream stream = nca.OpenSection(index, false, verify);
Stream stream = nca.OpenSection(index, false, integrityCheckLevel);
switch (section.Type)
{
@ -379,5 +461,53 @@ namespace LibHac
break;
}
}
public static Validity VerifyNca(this Nca nca, IProgressReport logger = null, bool quiet = false)
{
for (int i = 0; i < 3; i++)
{
if (nca.Sections[i] != null)
{
Validity sectionValidity = VerifySection(nca, i, logger, quiet);
if (sectionValidity == Validity.Invalid) return Validity.Invalid;
}
}
return Validity.Valid;
}
public static Validity VerifySection(this Nca nca, int index, IProgressReport logger = null, bool quiet = false)
{
if (nca.Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index));
NcaSection sect = nca.Sections[index];
NcaHashType hashType = sect.Header.HashType;
if (hashType != NcaHashType.Sha256 && hashType != NcaHashType.Ivfc) return Validity.Unchecked;
HierarchicalIntegrityVerificationStream stream = nca.OpenHashedSection(index, IntegrityCheckLevel.IgnoreOnInvalid);
if (stream == null) return Validity.Unchecked;
if (!quiet) logger?.LogMessage($"Verifying section {index}...");
for (int i = 0; i < stream.Levels.Length - 1; i++)
{
if (!quiet) logger?.LogMessage($" Verifying Hash Level {i}...");
Validity levelValidity = stream.ValidateLevel(i, true, logger);
if (hashType == NcaHashType.Ivfc)
{
sect.Header.IvfcInfo.LevelHeaders[i].HashValidity = levelValidity;
}
else if (hashType == NcaHashType.Sha256 && i == stream.Levels.Length - 2)
{
sect.Header.Sha256Info.HashValidity = levelValidity;
}
if (levelValidity == Validity.Invalid) return Validity.Invalid;
}
return Validity.Valid;
}
}
}

View file

@ -148,32 +148,6 @@ namespace LibHac
}
}
public class RomfsSuperblock
{
public IvfcHeader IvfcHeader;
public RomfsSuperblock(BinaryReader reader)
{
IvfcHeader = new IvfcHeader(reader);
reader.BaseStream.Position += 0x58;
}
}
public class BktrSuperblock
{
public IvfcHeader IvfcHeader;
public BktrHeader RelocationHeader;
public BktrHeader SubsectionHeader;
public BktrSuperblock(BinaryReader reader)
{
IvfcHeader = new IvfcHeader(reader);
reader.BaseStream.Position += 0x18;
RelocationHeader = new BktrHeader(reader);
SubsectionHeader = new BktrHeader(reader);
}
}
public class BktrPatchInfo
{
public BktrHeader RelocationHeader;
@ -215,6 +189,8 @@ namespace LibHac
public int BlockSizePower;
public uint Reserved;
public Validity HashValidity = Validity.Unchecked;
public IvfcLevelHeader(BinaryReader reader)
{
LogicalOffset = reader.ReadInt64();
@ -234,6 +210,8 @@ namespace LibHac
public long DataOffset;
public long DataSize;
public Validity HashValidity = Validity.Unchecked;
public Sha256Info(BinaryReader reader)
{
MasterHash = reader.ReadBytes(0x20);
@ -300,17 +278,12 @@ namespace LibHac
}
}
public class Pfs0Section
public enum ProgramPartitionType
{
public PfsSuperblock 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];
}
Code,
Data,
Logo
};
public enum ContentType
{
@ -359,10 +332,11 @@ namespace LibHac
Bktr
}
public enum Validity
public enum Validity : byte
{
Unchecked,
Invalid,
Valid
Valid,
MissingKey
}
}

View file

@ -68,29 +68,6 @@ namespace LibHac
Hfs0
}
public class PfsSuperblock
{
public byte[] MasterHash; /* SHA-256 hash of the hash table. */
public int BlockSize; /* In bytes. */
public uint Always2;
public long HashTableOffset; /* Normally zero. */
public long HashTableSize;
public long Pfs0Offset;
public long Pfs0Size;
public PfsSuperblock(BinaryReader reader)
{
MasterHash = reader.ReadBytes(0x20);
BlockSize = reader.ReadInt32();
Always2 = reader.ReadUInt32();
HashTableOffset = reader.ReadInt64();
HashTableSize = reader.ReadInt64();
Pfs0Offset = reader.ReadInt64();
Pfs0Size = reader.ReadInt64();
reader.BaseStream.Position += 0xF0;
}
}
public class PfsHeader
{
public string Magic;

View file

@ -143,19 +143,6 @@ namespace LibHac
}
}
public class IvfcLevel
{
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
{
public static void Extract(this Romfs romfs, string outDir, IProgressReport logger = null)

View file

@ -41,7 +41,7 @@ namespace LibHac.Savefile
public DirectoryEntry[] Directories { get; private set; }
private Dictionary<string, FileEntry> FileDict { get; }
public Savefile(Keyset keyset, Stream file, bool enableIntegrityChecks)
public Savefile(Keyset keyset, Stream file, IntegrityCheckLevel integrityCheckLevel)
{
SavefileSource = new SharedStreamSource(file);
@ -106,7 +106,7 @@ namespace LibHac.Savefile
JournalStream = new JournalStream(journalData, journalMap, (int)Header.Journal.BlockSize);
JournalStreamSource = new SharedStreamSource(JournalStream);
IvfcStream = InitIvfcStream(enableIntegrityChecks);
IvfcStream = InitIvfcStream(integrityCheckLevel);
IvfcStreamSource = new SharedStreamSource(IvfcStream);
ReadFileInfo();
@ -120,7 +120,7 @@ namespace LibHac.Savefile
}
}
private HierarchicalIntegrityVerificationStream InitIvfcStream(bool enableIntegrityChecks)
private HierarchicalIntegrityVerificationStream InitIvfcStream(IntegrityCheckLevel integrityCheckLevel)
{
IvfcHeader ivfc = Header.Ivfc;
@ -151,7 +151,7 @@ namespace LibHac.Savefile
};
}
return new HierarchicalIntegrityVerificationStream(initInfo, enableIntegrityChecks);
return new HierarchicalIntegrityVerificationStream(initInfo, integrityCheckLevel);
}
public Stream OpenFile(string filename)

View file

@ -83,7 +83,7 @@ namespace LibHac.Streams
break;
}
int idx = 0;
while (idx+1 < _streamsStartPos.Count)
while (idx + 1 < _streamsStartPos.Count)
{
if (_streamsStartPos[idx + 1] > pos)
{
@ -122,10 +122,11 @@ namespace LibHac.Streams
if (count > 0)
{
if (_currentStreamIndex >= _streams.Count)
if (_currentStreamIndex + 1 >= _streams.Count)
break;
_currentStream = _streams[_currentStreamIndex++];
_currentStream = _streams[_currentStreamIndex + 1];
_currentStreamIndex++;
_currentStream.Position = 0;
}
}

View file

@ -15,6 +15,11 @@ namespace LibHac.Streams
/// </summary>
public int SectorSize { get; }
/// <summary>
/// The number of sectors in the stream.
/// </summary>
public int SectorCount { get; }
/// <summary>
/// The maximum number of sectors that can be read or written in a single operation.
/// </summary>
@ -64,6 +69,8 @@ namespace LibHac.Streams
_keepOpen = keepOpen;
_maxBufferSize = MaxSectors * SectorSize;
baseStream.Position = offset;
SectorCount = (int)Util.DivideByRoundUp(_baseStream.Length - _offset, sectorSize);
}
public override void Flush()

View file

@ -126,7 +126,7 @@ namespace LibHac
string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/');
var nax0 = new Nax0(Keyset, stream, sdPath, false);
save = new Savefile.Savefile(Keyset, nax0.Stream, false);
save = new Savefile.Savefile(Keyset, nax0.Stream, IntegrityCheckLevel.None);
}
catch (Exception ex)
{
@ -147,7 +147,7 @@ namespace LibHac
var title = new Title();
// Meta contents always have 1 Partition FS section with 1 file in it
Stream sect = nca.OpenSection(0, false, true);
Stream sect = nca.OpenSection(0, false, IntegrityCheckLevel.ErrorOnInvalid);
var pfs0 = new Pfs(sect);
Stream file = pfs0.OpenFile(pfs0.Files[0]);
@ -187,7 +187,7 @@ namespace LibHac
{
foreach (Title title in Titles.Values.Where(x => x.ControlNca != null))
{
var romfs = new Romfs(title.ControlNca.OpenSection(0, false, true));
var romfs = new Romfs(title.ControlNca.OpenSection(0, false, IntegrityCheckLevel.ErrorOnInvalid));
byte[] control = romfs.GetFile("/control.nacp");
var reader = new BinaryReader(new MemoryStream(control));

View file

@ -399,10 +399,16 @@ namespace LibHac
case 2: return "3.0.1-3.0.2";
case 3: return "4.0.0-4.1.0";
case 4: return "5.0.0-5.1.0";
case 5: return "6.0.0";
case 5: return "6.0.0-6.0.1";
default: return "Unknown";
}
}
public static bool IsSubRange(long startIndex, long subLength, long length)
{
bool isOutOfRange = startIndex < 0 || startIndex > length || subLength < 0 || startIndex > length - subLength;
return !isOutOfRange;
}
}
public class ByteArray128BitComparer : EqualityComparer<byte[]>

View file

@ -102,7 +102,7 @@ namespace NandReader
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
{
var tickets = new List<Ticket>();
var save = new Savefile(keyset, savefile, false);
var save = new Savefile(keyset, savefile, IntegrityCheckLevel.None);
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin"));
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin"));

View file

@ -84,7 +84,7 @@ namespace NandReaderGui.ViewModel
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
{
var tickets = new List<Ticket>();
var save = new Savefile(keyset, savefile, false);
var save = new Savefile(keyset, savefile, IntegrityCheckLevel.None);
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin"));
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin"));

View file

@ -15,9 +15,10 @@ hactoolnet is an example program that uses LibHac. It is used in a similar manne
Usage: hactoolnet.exe [options...] <path>
Options:
-r, --raw Keep raw data, don't unpack.
-y, --verify Verify hashes.
-y, --verify Verify all hashes in the input file.
-h, --enablehash Enable hash checks when reading the input file.
-k, --keyset Load keys from an external file.
-t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, switchfs, save, keygen]
-t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, ini1, kip1, switchfs, save, keygen]
--titlekeys <file> Load title keys from an external file.
NCA options:
--section0 <file> Specify Section 0 file path.
@ -53,6 +54,8 @@ Package1 options:
--outdir <dir> Specify Package1 directory path.
Package2 options:
--outdir <dir> Specify Package2 directory path.
INI1 options:
--outdir <dir> Specify INI1 directory path.
Switch FS options:
--sdseed <seed> Set console unique seed for SD card NAX0 encryption.
--listapps List application info.
@ -64,10 +67,13 @@ Switch FS options:
--romfs <file> Specify RomFS directory path. (--title must be specified)
--romfsdir <dir> Specify RomFS directory path. (--title must be specified)
--savedir <dir> Specify save file directory path.
-y, --verify Verify all titles, or verify a single title if --title is set.
Savefile options:
--outdir <dir> Specify directory path to save contents to.
--debugoutdir <dir> Specify directory path to save intermediate data to for debugging.
--sign Sign the save file. (Requires device_key in key file)
Keygen options:
--outdir <dir> Specify directory path to save key files to.
```
## Examples

View file

@ -198,6 +198,7 @@ namespace hactoolnet
sb.AppendLine(" --romfs <file> Specify RomFS directory path. (--title must be specified)");
sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path. (--title must be specified)");
sb.AppendLine(" --savedir <dir> Specify save file directory path.");
sb.AppendLine(" -y, --verify Verify all titles, or verify a single title if --title is set.");
sb.AppendLine("Savefile options:");
sb.AppendLine(" --outdir <dir> Specify directory path to save contents to.");
sb.AppendLine(" --debugoutdir <dir> Specify directory path to save intermediate data to for debugging.");

View file

@ -36,6 +36,9 @@ namespace hactoolnet
public bool ListRomFs;
public bool SignSave;
public ulong TitleId;
public IntegrityCheckLevel IntegrityLevel =>
EnableHash ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
}
internal enum FileType

View file

@ -13,6 +13,7 @@ namespace hactoolnet
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
{
var nca = new Nca(ctx.Keyset, file, false);
nca.ValidateMasterHashes();
if (ctx.Options.BaseNca != null)
{
@ -25,12 +26,12 @@ namespace hactoolnet
{
if (ctx.Options.SectionOut[i] != null)
{
nca.ExportSection(i, ctx.Options.SectionOut[i], ctx.Options.Raw, ctx.Options.EnableHash, ctx.Logger);
nca.ExportSection(i, ctx.Options.SectionOut[i], ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
}
if (ctx.Options.SectionOutDir[i] != null)
{
nca.ExtractSection(i, ctx.Options.SectionOutDir[i], ctx.Options.EnableHash, ctx.Logger);
nca.ExtractSection(i, ctx.Options.SectionOutDir[i], ctx.Options.IntegrityLevel, ctx.Logger);
}
if (ctx.Options.Validate && nca.Sections[i] != null)
@ -41,7 +42,7 @@ namespace hactoolnet
if (ctx.Options.ListRomFs && nca.Sections[1] != null)
{
var romfs = new Romfs(nca.OpenSection(1, false, ctx.Options.EnableHash));
var romfs = new Romfs(nca.OpenSection(1, false, ctx.Options.IntegrityLevel));
foreach (RomfsFile romfsFile in romfs.Files)
{
@ -67,19 +68,25 @@ namespace hactoolnet
if (ctx.Options.RomfsOut != null)
{
nca.ExportSection(section.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Options.EnableHash, ctx.Logger);
nca.ExportSection(section.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
}
if (ctx.Options.RomfsOutDir != null)
{
var romfs = new Romfs(nca.OpenSection(section.SectionNum, false, ctx.Options.EnableHash));
var romfs = new Romfs(nca.OpenSection(section.SectionNum, false, ctx.Options.IntegrityLevel));
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
}
}
if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null)
{
NcaSection section = nca.Sections.FirstOrDefault(x => x?.IsExefs == true);
if (nca.Header.ContentType != ContentType.Program)
{
ctx.Logger.LogMessage("NCA's content type is not \"Program\"");
return;
}
NcaSection section = nca.Sections[(int)ProgramPartitionType.Code];
if (section == null)
{
@ -89,12 +96,12 @@ namespace hactoolnet
if (ctx.Options.ExefsOut != null)
{
nca.ExportSection(section.SectionNum, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.EnableHash, ctx.Logger);
nca.ExportSection(section.SectionNum, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
}
if (ctx.Options.ExefsOutDir != null)
{
nca.ExtractSection(section.SectionNum, ctx.Options.ExefsOutDir, ctx.Options.EnableHash, ctx.Logger);
nca.ExtractSection(section.SectionNum, ctx.Options.ExefsOutDir, ctx.Options.IntegrityLevel, ctx.Logger);
}
}
@ -153,21 +160,21 @@ namespace hactoolnet
NcaSection sect = nca.Sections[i];
if (sect == null) continue;
bool isExefs = nca.Header.ContentType == ContentType.Program && i == (int)ProgramPartitionType.Code;
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, " Partition Type:", isExefs ? "ExeFS" : sect.Type.ToString());
PrintItem(sb, colLen, " Section CTR:", sect.Header.Ctr);
switch (sect.Type)
switch (sect.Header.HashType)
{
case SectionType.Pfs0:
PrintPfs0(sect);
case NcaHashType.Sha256:
PrintSha256Hash(sect);
break;
case SectionType.Romfs:
PrintRomfs(sect);
break;
case SectionType.Bktr:
case NcaHashType.Ivfc:
PrintIvfcHash(sect);
break;
default:
sb.AppendLine(" Unknown/invalid superblock!");
@ -176,13 +183,12 @@ namespace hactoolnet
}
}
void PrintPfs0(NcaSection sect)
void PrintSha256Hash(NcaSection sect)
{
Sha256Info hashInfo = sect.Header.Sha256Info;
PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", hashInfo.MasterHash);
// todo sb.AppendLine($" Hash Table{sect.Pfs0.Validity.GetValidityString()}:");
sb.AppendLine($" Hash Table:");
PrintItem(sb, colLen, $" Master Hash{sect.MasterHashValidity.GetValidityString()}:", hashInfo.MasterHash);
sb.AppendLine($" Hash Table{sect.Header.Sha256Info.HashValidity.GetValidityString()}:");
PrintItem(sb, colLen, " Offset:", $"0x{hashInfo.HashTableOffset:x12}");
PrintItem(sb, colLen, " Size:", $"0x{hashInfo.HashTableSize:x12}");
@ -191,11 +197,11 @@ namespace hactoolnet
PrintItem(sb, colLen, " PFS0 Size:", $"0x{hashInfo.DataSize:x12}");
}
void PrintRomfs(NcaSection sect)
void PrintIvfcHash(NcaSection sect)
{
IvfcHeader ivfcInfo = sect.Header.IvfcInfo;
PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", ivfcInfo.MasterHash);
PrintItem(sb, colLen, $" Master Hash{sect.MasterHashValidity.GetValidityString()}:", ivfcInfo.MasterHash);
PrintItem(sb, colLen, " Magic:", ivfcInfo.Magic);
PrintItem(sb, colLen, " Version:", $"{ivfcInfo.Version:x8}");
@ -209,8 +215,7 @@ namespace hactoolnet
hashOffset = ivfcInfo.LevelHeaders[i - 1].LogicalOffset;
}
// todo sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:");
sb.AppendLine($" Level {i}:");
sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:");
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}");

View file

@ -14,7 +14,7 @@ namespace hactoolnet
{
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.ReadWrite))
{
var save = new Savefile(ctx.Keyset, file, ctx.Options.EnableHash);
var save = new Savefile(ctx.Keyset, file, ctx.Options.IntegrityLevel);
if (ctx.Options.OutDir != null)
{

View file

@ -45,7 +45,7 @@ namespace hactoolnet
return;
}
NcaSection section = title.MainNca.Sections.FirstOrDefault(x => x.IsExefs);
NcaSection section = title.MainNca.Sections[(int)ProgramPartitionType.Code];
if (section == null)
{
@ -55,12 +55,12 @@ namespace hactoolnet
if (ctx.Options.ExefsOutDir != null)
{
title.MainNca.ExtractSection(section.SectionNum, ctx.Options.ExefsOutDir, ctx.Options.EnableHash, ctx.Logger);
title.MainNca.ExtractSection(section.SectionNum, ctx.Options.ExefsOutDir, ctx.Options.IntegrityLevel, ctx.Logger);
}
if (ctx.Options.ExefsOut != null)
{
title.MainNca.ExportSection(section.SectionNum, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.EnableHash, ctx.Logger);
title.MainNca.ExportSection(section.SectionNum, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
}
}
@ -95,13 +95,13 @@ namespace hactoolnet
if (ctx.Options.RomfsOutDir != null)
{
var romfs = new Romfs(title.MainNca.OpenSection(section.SectionNum, false, ctx.Options.EnableHash));
var romfs = new Romfs(title.MainNca.OpenSection(section.SectionNum, false, ctx.Options.IntegrityLevel));
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
}
if (ctx.Options.RomfsOut != null)
{
title.MainNca.ExportSection(section.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Options.EnableHash, ctx.Logger);
title.MainNca.ExportSection(section.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
}
}
@ -119,6 +119,67 @@ namespace hactoolnet
{
ExportSdSaves(ctx, switchFs);
}
if (ctx.Options.Validate)
{
ValidateSwitchFs(ctx, switchFs);
}
}
private static void ValidateSwitchFs(Context ctx, SwitchFs switchFs)
{
if (ctx.Options.TitleId != 0)
{
ulong id = ctx.Options.TitleId;
if (!switchFs.Titles.TryGetValue(id, out Title title))
{
ctx.Logger.LogMessage($"Could not find title {id:X16}");
return;
}
ValidateTitle(ctx, title, "");
return;
}
foreach (Application app in switchFs.Applications.Values)
{
ctx.Logger.LogMessage($"Checking {app.Name}...");
Title mainTitle = app.Patch ?? app.Main;
if (mainTitle != null)
{
ValidateTitle(ctx, mainTitle, "Main title");
}
foreach (Title title in app.AddOnContent)
{
ValidateTitle(ctx, title, "Add-on content");
}
}
}
private static void ValidateTitle(Context ctx, Title title, string caption)
{
try
{
ctx.Logger.LogMessage($" {caption} {title.Id:x16}");
foreach (Nca nca in title.Ncas)
{
ctx.Logger.LogMessage($" {nca.Header.ContentType.ToString()}");
Validity validity = nca.VerifyNca(ctx.Logger, true);
ctx.Logger.LogMessage($" {validity.ToString()}");
}
}
catch (Exception ex)
{
ctx.Logger.LogMessage($"Error processing title {title.Id:x16}:\n{ex.Message}");
}
}
private static void SaveTitle(Context ctx, SwitchFs switchFs)
@ -170,7 +231,7 @@ namespace hactoolnet
foreach (NcaSection sect in nca.Sections.Where(x => x != null))
{
Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.EncryptionType} {sect.SuperblockHashValidity}");
Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.EncryptionType} {sect.MasterHashValidity}");
}
}

View file

@ -69,7 +69,7 @@ namespace hactoolnet
return;
}
NcaSection exefsSection = mainNca.Sections.FirstOrDefault(x => x.IsExefs);
NcaSection exefsSection = mainNca.Sections[(int)ProgramPartitionType.Code];
if (exefsSection == null)
{
@ -79,12 +79,12 @@ namespace hactoolnet
if (ctx.Options.ExefsOutDir != null)
{
mainNca.ExtractSection(exefsSection.SectionNum, ctx.Options.ExefsOutDir, ctx.Options.EnableHash, ctx.Logger);
mainNca.ExtractSection(exefsSection.SectionNum, ctx.Options.ExefsOutDir, ctx.Options.IntegrityLevel, ctx.Logger);
}
if (ctx.Options.ExefsOut != null)
{
mainNca.ExportSection(exefsSection.SectionNum, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.EnableHash, ctx.Logger);
mainNca.ExportSection(exefsSection.SectionNum, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
}
}
@ -108,13 +108,13 @@ namespace hactoolnet
if (ctx.Options.RomfsOutDir != null)
{
var romfs = new Romfs(mainNca.OpenSection(romfsSection.SectionNum, false, ctx.Options.EnableHash));
var romfs = new Romfs(mainNca.OpenSection(romfsSection.SectionNum, false, ctx.Options.IntegrityLevel));
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
}
if (ctx.Options.RomfsOut != null)
{
mainNca.ExportSection(romfsSection.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Options.EnableHash, ctx.Logger);
mainNca.ExportSection(romfsSection.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
}
}
}

View file

@ -8,6 +8,27 @@ namespace hactoolnet
public static class Program
{
public static void Main(string[] args)
{
try
{
Run(args);
}
catch (MissingKeyException ex)
{
string name = ex.Type == KeyType.Title ? $"Title key for rights ID {ex.Name}" : ex.Name;
Console.WriteLine($"\nERROR: {ex.Message}\nA required key is missing.\nKey name: {name}\n");
}
catch (Exception ex)
{
Console.WriteLine($"\nERROR: {ex.Message}\n");
Console.WriteLine("Additional information:");
Console.WriteLine(ex.GetType().FullName);
Console.WriteLine(ex.StackTrace);
}
}
private static void Run(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
var ctx = new Context();
@ -25,47 +46,52 @@ namespace hactoolnet
return;
}
switch (ctx.Options.InFileType)
{
case FileType.Nca:
ProcessNca.Process(ctx);
break;
case FileType.Pfs0:
case FileType.Nsp:
ProcessNsp.Process(ctx);
break;
case FileType.Romfs:
ProcessRomfs.Process(ctx);
break;
case FileType.Nax0:
break;
case FileType.SwitchFs:
ProcessSwitchFs.Process(ctx);
break;
case FileType.Save:
ProcessSave.Process(ctx);
break;
case FileType.Xci:
ProcessXci.Process(ctx);
break;
case FileType.Keygen:
ProcessKeygen(ctx);
break;
case FileType.Pk11:
ProcessPackage.ProcessPk11(ctx);
break;
case FileType.Pk21:
ProcessPackage.ProcessPk21(ctx);
break;
case FileType.Kip1:
ProcessKip.ProcessKip1(ctx);
break;
case FileType.Ini1:
ProcessKip.ProcessIni1(ctx);
break;
default:
throw new ArgumentOutOfRangeException();
}
RunTask(ctx);
}
}
private static void RunTask(Context ctx)
{
switch (ctx.Options.InFileType)
{
case FileType.Nca:
ProcessNca.Process(ctx);
break;
case FileType.Pfs0:
case FileType.Nsp:
ProcessNsp.Process(ctx);
break;
case FileType.Romfs:
ProcessRomfs.Process(ctx);
break;
case FileType.Nax0:
break;
case FileType.SwitchFs:
ProcessSwitchFs.Process(ctx);
break;
case FileType.Save:
ProcessSave.Process(ctx);
break;
case FileType.Xci:
ProcessXci.Process(ctx);
break;
case FileType.Keygen:
ProcessKeygen(ctx);
break;
case FileType.Pk11:
ProcessPackage.ProcessPk11(ctx);
break;
case FileType.Pk21:
ProcessPackage.ProcessPk21(ctx);
break;
case FileType.Kip1:
ProcessKip.ProcessKip1(ctx);
break;
case FileType.Ini1:
ProcessKip.ProcessIni1(ctx);
break;
default:
throw new ArgumentOutOfRangeException();
}
}