mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Continue NCA improvements
- Read only the NCA header when first opening an NCA. This allows for reading of partial NCAs and slightly improves performance when opening an NCA. - Add a separate NCA method to validate master hashes now that it's not automatically done when opening the NCA. - Fix possible hang when reading BKTR sections. - When keys required to decrypt an NCA are missing, throw an exception with information about the missing keys. - Add more sanity checks when reading an NCA. Hactoolnet: Don't hard-crash when hitting an unhandled exception
This commit is contained in:
parent
209d81187a
commit
a64cbeca5b
8 changed files with 257 additions and 108 deletions
|
@ -78,6 +78,8 @@ namespace LibHac
|
||||||
int bytesToRead = (int)Math.Min(CurrentEntry.OffsetEnd - Position, count);
|
int bytesToRead = (int)Math.Min(CurrentEntry.OffsetEnd - Position, count);
|
||||||
int bytesRead = base.Read(buffer, outPos, bytesToRead);
|
int bytesRead = base.Read(buffer, outPos, bytesToRead);
|
||||||
|
|
||||||
|
if (bytesRead == 0) break;
|
||||||
|
|
||||||
outPos += bytesRead;
|
outPos += bytesRead;
|
||||||
totalBytesRead += bytesRead;
|
totalBytesRead += bytesRead;
|
||||||
count -= bytesRead;
|
count -= bytesRead;
|
||||||
|
|
|
@ -246,7 +246,7 @@ namespace LibHac
|
||||||
Crypto.DecryptEcb(headerKek, HeaderKeySource, HeaderKey, 0x20);
|
Crypto.DecryptEcb(headerKek, HeaderKeySource, HeaderKey, 0x20);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DeriveSdCardKeys()
|
public void DeriveSdCardKeys()
|
||||||
{
|
{
|
||||||
var sdKek = new byte[0x10];
|
var sdKek = new byte[0x10];
|
||||||
Crypto.GenerateKek(MasterKeys[0], SdCardKekSource, sdKek, AesKekGenerationSource, AesKeyGenerationSource);
|
Crypto.GenerateKek(MasterKeys[0], SdCardKekSource, sdKek, AesKekGenerationSource, AesKeyGenerationSource);
|
||||||
|
@ -264,6 +264,8 @@ namespace LibHac
|
||||||
Crypto.DecryptEcb(sdKek, SdCardKeySourcesSpecific[k], SdCardKeys[k], 0x20);
|
Crypto.DecryptEcb(sdKek, SdCardKeySourcesSpecific[k], SdCardKeys[k], 0x20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static readonly string[] KakNames = {"application", "ocean", "system"};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExternalKeys
|
public static class ExternalKeys
|
||||||
|
@ -396,7 +398,7 @@ 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;
|
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);
|
||||||
|
|
210
LibHac/Nca.cs
210
LibHac/Nca.cs
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using LibHac.Streams;
|
using LibHac.Streams;
|
||||||
using LibHac.XTSSharp;
|
using LibHac.XTSSharp;
|
||||||
|
|
||||||
|
@ -20,6 +19,9 @@ namespace LibHac
|
||||||
private bool KeepOpen { get; }
|
private bool KeepOpen { get; }
|
||||||
private Nca BaseNca { get; set; }
|
private Nca BaseNca { get; set; }
|
||||||
|
|
||||||
|
private bool IsMissingTitleKey { get; set; }
|
||||||
|
private string MissingKeyName { get; set; }
|
||||||
|
|
||||||
public NcaSection[] Sections { get; } = new NcaSection[4];
|
public NcaSection[] Sections { get; } = new NcaSection[4];
|
||||||
|
|
||||||
public Nca(Keyset keyset, Stream stream, bool keepOpen)
|
public Nca(Keyset keyset, Stream stream, bool keepOpen)
|
||||||
|
@ -38,19 +40,20 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
DecryptKeyArea(keyset);
|
DecryptKeyArea(keyset);
|
||||||
}
|
}
|
||||||
else
|
else if (keyset.TitleKeys.TryGetValue(Header.RightsId, out byte[] titleKey))
|
||||||
{
|
{
|
||||||
if (keyset.TitleKeys.TryGetValue(Header.RightsId, out byte[] titleKey))
|
if (keyset.Titlekeks[CryptoType].IsEmpty())
|
||||||
{
|
{
|
||||||
|
MissingKeyName = $"titlekek_{CryptoType:x2}";
|
||||||
|
}
|
||||||
|
|
||||||
TitleKey = titleKey;
|
TitleKey = titleKey;
|
||||||
Crypto.DecryptEcb(keyset.Titlekeks[CryptoType], titleKey, TitleKeyDec, 0x10);
|
Crypto.DecryptEcb(keyset.Titlekeks[CryptoType], titleKey, TitleKeyDec, 0x10);
|
||||||
DecryptedKeys[2] = TitleKeyDec;
|
DecryptedKeys[2] = TitleKeyDec;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// todo enable key check when opening a section
|
IsMissingTitleKey = true;
|
||||||
// throw new MissingKeyException("A required key is missing.", $"{Header.RightsId.ToHexString()}", KeyType.Title);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
|
@ -58,36 +61,69 @@ namespace LibHac
|
||||||
NcaSection section = ParseSection(i);
|
NcaSection section = ParseSection(i);
|
||||||
if (section == null) continue;
|
if (section == null) continue;
|
||||||
Sections[i] = section;
|
Sections[i] = section;
|
||||||
ValidateSuperblockHash(i);
|
ValidateMasterHash(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (NcaSection pfsSection in Sections.Where(x => x != null && x.Type == SectionType.Pfs0))
|
//foreach (NcaSection pfsSection in Sections.Where(x => x != null && x.Type == SectionType.Pfs0))
|
||||||
{
|
//{
|
||||||
Stream sectionStream = OpenSection(pfsSection.SectionNum, false, false);
|
// Stream sectionStream = OpenSection(pfsSection.SectionNum, false, false);
|
||||||
if (sectionStream == null) continue;
|
// if (sectionStream == null) continue;
|
||||||
|
|
||||||
var pfs = new Pfs(sectionStream);
|
// var pfs = new Pfs(sectionStream);
|
||||||
if (!pfs.FileExists("main.npdm")) continue;
|
// if (!pfs.FileExists("main.npdm")) continue;
|
||||||
|
|
||||||
pfsSection.IsExefs = true;
|
// 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()
|
public Stream GetStream()
|
||||||
{
|
{
|
||||||
return StreamSource.CreateStream();
|
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)
|
private Stream OpenRawSection(int index)
|
||||||
{
|
{
|
||||||
NcaSection sect = Sections[index];
|
if (index < 0 || index > 3) throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
if (sect == null) 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 offset = sect.Offset;
|
||||||
long size = sect.Size;
|
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);
|
Stream rawStream = StreamSource.CreateStream(offset, size);
|
||||||
|
|
||||||
switch (sect.Header.EncryptionType)
|
switch (sect.Header.EncryptionType)
|
||||||
|
@ -104,10 +140,9 @@ namespace LibHac
|
||||||
false);
|
false);
|
||||||
if (BaseNca == null) return rawStream;
|
if (BaseNca == null) return rawStream;
|
||||||
|
|
||||||
NcaSection baseSect = BaseNca.Sections.FirstOrDefault(x => x.Type == SectionType.Romfs);
|
Stream baseStream = BaseNca.OpenSection(ProgramPartitionType.Data, true, false);
|
||||||
if (baseSect == null) throw new InvalidDataException("Base NCA has no RomFS section");
|
if (baseStream == null) throw new InvalidDataException("Base NCA has no RomFS section");
|
||||||
|
|
||||||
Stream baseStream = BaseNca.OpenSection(baseSect.SectionNum, true, false);
|
|
||||||
return new Bktr(rawStream, baseStream, sect);
|
return new Bktr(rawStream, baseStream, sect);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -115,25 +150,50 @@ namespace LibHac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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="enableIntegrityChecks"><see langword="true"/> to enable data integrity checks when reading the section.
|
||||||
|
/// Only applies 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, bool enableIntegrityChecks)
|
public Stream OpenSection(int index, bool raw, bool enableIntegrityChecks)
|
||||||
{
|
{
|
||||||
Stream rawStream = OpenRawSection(index);
|
Stream rawStream = OpenRawSection(index);
|
||||||
NcaSection sect = Sections[index];
|
NcaSection sect = Sections[index];
|
||||||
|
NcaFsHeader header = sect.Header;
|
||||||
|
|
||||||
if (raw || rawStream == null) return rawStream;
|
if (raw || rawStream == null) return rawStream;
|
||||||
|
|
||||||
switch (sect.Header.Type)
|
// 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:
|
case NcaHashType.Sha256:
|
||||||
return InitIvfcForPartitionfs(sect.Header.Sha256Info, new SharedStreamSource(rawStream), enableIntegrityChecks);
|
return InitIvfcForPartitionfs(header.Sha256Info, new SharedStreamSource(rawStream), enableIntegrityChecks);
|
||||||
case SectionType.Romfs:
|
case NcaHashType.Ivfc:
|
||||||
case SectionType.Bktr:
|
return InitIvfcForRomfs(header.IvfcInfo, new SharedStreamSource(rawStream), enableIntegrityChecks);
|
||||||
return InitIvfcForRomfs(sect.Header.IvfcInfo, new SharedStreamSource(rawStream), enableIntegrityChecks);
|
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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="enableIntegrityChecks"><see langword="true"/> to enable data integrity checks when reading the section.
|
||||||
|
/// Only applies 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, bool enableIntegrityChecks) =>
|
||||||
|
OpenSection((int)type, raw, enableIntegrityChecks);
|
||||||
|
|
||||||
private static HierarchicalIntegrityVerificationStream InitIvfcForRomfs(IvfcHeader ivfc,
|
private static HierarchicalIntegrityVerificationStream InitIvfcForRomfs(IvfcHeader ivfc,
|
||||||
SharedStreamSource romfsStreamSource, bool enableIntegrityChecks)
|
SharedStreamSource romfsStreamSource, bool enableIntegrityChecks)
|
||||||
{
|
{
|
||||||
|
@ -195,10 +255,31 @@ namespace LibHac
|
||||||
return new HierarchicalIntegrityVerificationStream(initInfo, enableIntegrityChecks);
|
return new HierarchicalIntegrityVerificationStream(initInfo, enableIntegrityChecks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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;
|
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)
|
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];
|
var headerBytes = new byte[0xC00];
|
||||||
Xts xts = XtsAes128.Create(keyset.HeaderKey);
|
Xts xts = XtsAes128.Create(keyset.HeaderKey);
|
||||||
using (var headerDec = new RandomAccessSectorStream(new XtsSectorStream(stream, xts, 0x200)))
|
using (var headerDec = new RandomAccessSectorStream(new XtsSectorStream(stream, xts, 0x200)))
|
||||||
|
@ -213,6 +294,12 @@ namespace LibHac
|
||||||
|
|
||||||
private void DecryptKeyArea(Keyset keyset)
|
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++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
Crypto.DecryptEcb(keyset.KeyAreaKeys[CryptoType][Header.KaekInd], Header.EncryptedKeys[i],
|
Crypto.DecryptEcb(keyset.KeyAreaKeys[CryptoType][Header.KaekInd], Header.EncryptedKeys[i],
|
||||||
|
@ -239,6 +326,10 @@ namespace LibHac
|
||||||
|
|
||||||
private void CheckBktrKey(NcaSection sect)
|
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;
|
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)))
|
||||||
{
|
{
|
||||||
|
@ -248,17 +339,23 @@ namespace LibHac
|
||||||
|
|
||||||
if (size != offset)
|
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));
|
if (Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
NcaSection sect = Sections[index];
|
NcaSection sect = Sections[index];
|
||||||
|
|
||||||
byte[] expected = null;
|
if (!CanOpenSection(index))
|
||||||
|
{
|
||||||
|
sect.MasterHashValidity = Validity.MissingKey;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] expected = sect.GetMasterHash();
|
||||||
long offset = 0;
|
long offset = 0;
|
||||||
long size = 0;
|
long size = 0;
|
||||||
|
|
||||||
|
@ -267,16 +364,12 @@ namespace LibHac
|
||||||
case SectionType.Invalid:
|
case SectionType.Invalid:
|
||||||
break;
|
break;
|
||||||
case SectionType.Pfs0:
|
case SectionType.Pfs0:
|
||||||
Sha256Info pfs0 = sect.Header.Sha256Info;
|
offset = sect.Header.Sha256Info.HashTableOffset;
|
||||||
expected = pfs0.MasterHash;
|
size = sect.Header.Sha256Info.HashTableSize;
|
||||||
offset = pfs0.HashTableOffset;
|
|
||||||
size = pfs0.HashTableSize;
|
|
||||||
break;
|
break;
|
||||||
case SectionType.Romfs:
|
case SectionType.Romfs:
|
||||||
IvfcHeader ivfc = sect.Header.IvfcInfo;
|
offset = sect.Header.IvfcInfo.LevelHeaders[0].LogicalOffset;
|
||||||
expected = ivfc.MasterHash;
|
size = 1 << sect.Header.IvfcInfo.LevelHeaders[0].BlockSizePower;
|
||||||
offset = ivfc.LevelHeaders[0].LogicalOffset;
|
|
||||||
size = 1 << ivfc.LevelHeaders[0].BlockSizePower;
|
|
||||||
break;
|
break;
|
||||||
case SectionType.Bktr:
|
case SectionType.Bktr:
|
||||||
CheckBktrKey(sect);
|
CheckBktrKey(sect);
|
||||||
|
@ -284,36 +377,30 @@ namespace LibHac
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream stream = OpenSection(index, true, false);
|
Stream stream = OpenSection(index, true, false);
|
||||||
if (stream == null) return;
|
|
||||||
if (expected == null) return;
|
|
||||||
|
|
||||||
var hashTable = new byte[size];
|
var hashTable = new byte[size];
|
||||||
stream.Position = offset;
|
stream.Position = offset;
|
||||||
stream.Read(hashTable, 0, hashTable.Length);
|
stream.Read(hashTable, 0, hashTable.Length);
|
||||||
|
|
||||||
sect.SuperblockHashValidity = Crypto.CheckMemoryHashTable(hashTable, expected, 0, hashTable.Length);
|
sect.MasterHashValidity = Crypto.CheckMemoryHashTable(hashTable, expected, 0, hashTable.Length);
|
||||||
// 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)
|
||||||
{
|
{
|
||||||
if (Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index));
|
if (Sections[index] == null) throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
|
||||||
NcaSection sect = Sections[index];
|
NcaSection sect = Sections[index];
|
||||||
Stream stream = OpenSection(index, true, false);
|
Stream stream = OpenSection(index, false, true);
|
||||||
logger?.LogMessage($"Verifying section {index}...");
|
logger?.LogMessage($"Verifying section {index}...");
|
||||||
|
|
||||||
switch (sect.Type)
|
switch (sect.Header.HashType)
|
||||||
{
|
{
|
||||||
case SectionType.Invalid:
|
case NcaHashType.Sha256:
|
||||||
break;
|
break;
|
||||||
case SectionType.Pfs0:
|
case NcaHashType.Ivfc:
|
||||||
// todo VerifyPfs0(stream, sect.Pfs0, logger);
|
|
||||||
break;
|
|
||||||
case SectionType.Romfs:
|
|
||||||
// todo VerifyIvfc(stream, sect.Romfs.IvfcLevels, logger);
|
|
||||||
break;
|
|
||||||
case SectionType.Bktr:
|
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,9 +420,26 @@ namespace LibHac
|
||||||
public int SectionNum { get; set; }
|
public int SectionNum { get; set; }
|
||||||
public long Offset { get; set; }
|
public long Offset { get; set; }
|
||||||
public long Size { get; set; }
|
public long Size { get; set; }
|
||||||
public Validity SuperblockHashValidity { get; set; }
|
public Validity MasterHashValidity { get; set; }
|
||||||
|
|
||||||
public bool IsExefs { get; internal 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 class NcaExtensions
|
||||||
|
|
|
@ -312,6 +312,13 @@ namespace LibHac
|
||||||
public IvfcLevel[] IvfcLevels { get; set; } = new IvfcLevel[Romfs.IvfcMaxLevel];
|
public IvfcLevel[] IvfcLevels { get; set; } = new IvfcLevel[Romfs.IvfcMaxLevel];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ProgramPartitionType
|
||||||
|
{
|
||||||
|
Code,
|
||||||
|
Data,
|
||||||
|
Logo
|
||||||
|
};
|
||||||
|
|
||||||
public enum ContentType
|
public enum ContentType
|
||||||
{
|
{
|
||||||
Program,
|
Program,
|
||||||
|
@ -363,6 +370,7 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
Unchecked,
|
Unchecked,
|
||||||
Invalid,
|
Invalid,
|
||||||
Valid
|
Valid,
|
||||||
|
MissingKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -399,10 +399,16 @@ namespace LibHac
|
||||||
case 2: return "3.0.1-3.0.2";
|
case 2: return "3.0.1-3.0.2";
|
||||||
case 3: return "4.0.0-4.1.0";
|
case 3: return "4.0.0-4.1.0";
|
||||||
case 4: return "5.0.0-5.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";
|
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[]>
|
public class ByteArray128BitComparer : EqualityComparer<byte[]>
|
||||||
|
|
|
@ -13,6 +13,7 @@ namespace hactoolnet
|
||||||
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
|
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
|
||||||
{
|
{
|
||||||
var nca = new Nca(ctx.Keyset, file, false);
|
var nca = new Nca(ctx.Keyset, file, false);
|
||||||
|
nca.ValidateMasterHashes();
|
||||||
|
|
||||||
if (ctx.Options.BaseNca != null)
|
if (ctx.Options.BaseNca != null)
|
||||||
{
|
{
|
||||||
|
@ -180,7 +181,7 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
Sha256Info hashInfo = sect.Header.Sha256Info;
|
Sha256Info hashInfo = sect.Header.Sha256Info;
|
||||||
|
|
||||||
PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", hashInfo.MasterHash);
|
PrintItem(sb, colLen, $" Superblock Hash{sect.MasterHashValidity.GetValidityString()}:", hashInfo.MasterHash);
|
||||||
// todo sb.AppendLine($" Hash Table{sect.Pfs0.Validity.GetValidityString()}:");
|
// todo sb.AppendLine($" Hash Table{sect.Pfs0.Validity.GetValidityString()}:");
|
||||||
sb.AppendLine($" Hash Table:");
|
sb.AppendLine($" Hash Table:");
|
||||||
|
|
||||||
|
@ -195,7 +196,7 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
IvfcHeader ivfcInfo = sect.Header.IvfcInfo;
|
IvfcHeader ivfcInfo = sect.Header.IvfcInfo;
|
||||||
|
|
||||||
PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", ivfcInfo.MasterHash);
|
PrintItem(sb, colLen, $" Superblock Hash{sect.MasterHashValidity.GetValidityString()}:", ivfcInfo.MasterHash);
|
||||||
PrintItem(sb, colLen, " Magic:", ivfcInfo.Magic);
|
PrintItem(sb, colLen, " Magic:", ivfcInfo.Magic);
|
||||||
PrintItem(sb, colLen, " Version:", $"{ivfcInfo.Version:x8}");
|
PrintItem(sb, colLen, " Version:", $"{ivfcInfo.Version:x8}");
|
||||||
|
|
||||||
|
|
|
@ -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.EncryptionType} {sect.SuperblockHashValidity}");
|
Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.EncryptionType} {sect.MasterHashValidity}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,27 @@ namespace hactoolnet
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
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;
|
Console.OutputEncoding = Encoding.UTF8;
|
||||||
var ctx = new Context();
|
var ctx = new Context();
|
||||||
|
@ -25,6 +46,12 @@ namespace hactoolnet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RunTask(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunTask(Context ctx)
|
||||||
|
{
|
||||||
switch (ctx.Options.InFileType)
|
switch (ctx.Options.InFileType)
|
||||||
{
|
{
|
||||||
case FileType.Nca:
|
case FileType.Nca:
|
||||||
|
@ -67,7 +94,6 @@ namespace hactoolnet
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void OpenKeyset(Context ctx)
|
private static void OpenKeyset(Context ctx)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue