mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
ffd1af4c8e
24 changed files with 556 additions and 277 deletions
|
@ -19,7 +19,7 @@ namespace LibHac
|
||||||
|
|
||||||
public Bktr(Stream patchRomfs, Stream baseRomfs, NcaSection section)
|
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");
|
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");
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,10 @@ namespace LibHac
|
||||||
CurrentEntry = CurrentEntry.Next;
|
CurrentEntry = CurrentEntry.Next;
|
||||||
UpdateCounterSubsection(CurrentEntry.Counter);
|
UpdateCounterSubsection(CurrentEntry.Counter);
|
||||||
}
|
}
|
||||||
|
else if (bytesRead == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalBytesRead;
|
return totalBytesRead;
|
||||||
|
|
|
@ -8,25 +8,78 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
public Stream[] Levels { get; }
|
public Stream[] Levels { get; }
|
||||||
public Stream DataLevel { 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];
|
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;
|
Levels[0] = levelInfo[0].Data;
|
||||||
|
|
||||||
for (int i = 1; i < Levels.Length; i++)
|
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);
|
Levels[i] = new RandomAccessSectorStream(levelData);
|
||||||
|
LevelValidities[i - 1] = levelData.BlockValidities;
|
||||||
|
IntegrityStreams[i - 1] = levelData;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataLevel = Levels[Levels.Length - 1];
|
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()
|
public override void Flush()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|
|
@ -10,7 +10,8 @@ namespace LibHac
|
||||||
private const int DigestSize = 0x20;
|
private const int DigestSize = 0x20;
|
||||||
|
|
||||||
private Stream HashStream { get; }
|
private Stream HashStream { get; }
|
||||||
public bool EnableIntegrityChecks { get; }
|
public IntegrityCheckLevel IntegrityCheckLevel { get; }
|
||||||
|
public Validity[] BlockValidities { get; }
|
||||||
|
|
||||||
private byte[] Salt { get; }
|
private byte[] Salt { get; }
|
||||||
private IntegrityStreamType Type { get; }
|
private IntegrityStreamType Type { get; }
|
||||||
|
@ -18,13 +19,15 @@ namespace LibHac
|
||||||
private readonly byte[] _hashBuffer = new byte[DigestSize];
|
private readonly byte[] _hashBuffer = new byte[DigestSize];
|
||||||
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, IntegrityCheckLevel integrityCheckLevel)
|
||||||
: base(info.Data, info.BlockSize)
|
: base(info.Data, info.BlockSize)
|
||||||
{
|
{
|
||||||
HashStream = hashStream;
|
HashStream = hashStream;
|
||||||
EnableIntegrityChecks = enableIntegrityChecks;
|
IntegrityCheckLevel = integrityCheckLevel;
|
||||||
Salt = info.Salt;
|
Salt = info.Salt;
|
||||||
Type = info.Type;
|
Type = info.Type;
|
||||||
|
|
||||||
|
BlockValidities = new Validity[SectorCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Flush()
|
public override void Flush()
|
||||||
|
@ -55,12 +58,16 @@ namespace LibHac
|
||||||
throw new NotImplementedException();
|
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);
|
HashStream.Read(_hashBuffer, 0, DigestSize);
|
||||||
|
|
||||||
int bytesRead = base.Read(buffer, 0, count);
|
int bytesRead = base.Read(buffer, offset, count);
|
||||||
int bytesToHash = SectorSize;
|
int bytesToHash = SectorSize;
|
||||||
|
|
||||||
if (bytesRead == 0) return 0;
|
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 a hash is zero the data for the entire block is zero
|
||||||
if (Type == IntegrityStreamType.Save && _hashBuffer.IsEmpty())
|
if (Type == IntegrityStreamType.Save && _hashBuffer.IsEmpty())
|
||||||
{
|
{
|
||||||
Array.Clear(buffer, 0, SectorSize);
|
Array.Clear(buffer, offset, SectorSize);
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytesRead < SectorSize)
|
if (bytesRead < SectorSize)
|
||||||
{
|
{
|
||||||
// Pad out unused portion of block
|
// 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
|
// Partition FS hashes don't pad out an incomplete block
|
||||||
if (Type == IntegrityStreamType.PartitionFs)
|
if (Type == IntegrityStreamType.PartitionFs)
|
||||||
|
@ -84,7 +91,14 @@ namespace LibHac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EnableIntegrityChecks) return bytesRead;
|
if (BlockValidities[blockNum] == Validity.Invalid && integrityCheckLevel == IntegrityCheckLevel.ErrorOnInvalid)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Hash error!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integrityCheckLevel == IntegrityCheckLevel.None) return bytesRead;
|
||||||
|
|
||||||
|
if (BlockValidities[blockNum] != Validity.Unchecked) return bytesRead;
|
||||||
|
|
||||||
_hash.Initialize();
|
_hash.Initialize();
|
||||||
|
|
||||||
|
@ -93,7 +107,7 @@ namespace LibHac
|
||||||
_hash.TransformBlock(Salt, 0, Salt.Length, null, 0);
|
_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);
|
_hash.TransformFinalBlock(buffer, 0, 0);
|
||||||
|
|
||||||
byte[] hash = _hash.Hash;
|
byte[] hash = _hash.Hash;
|
||||||
|
@ -104,7 +118,10 @@ namespace LibHac
|
||||||
hash[0x1F] |= 0x80;
|
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!");
|
throw new InvalidDataException("Hash error!");
|
||||||
}
|
}
|
||||||
|
@ -139,4 +156,23 @@ namespace LibHac
|
||||||
RomFs,
|
RomFs,
|
||||||
PartitionFs
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
316
LibHac/Nca.cs
316
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 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
|
else
|
||||||
{
|
{
|
||||||
if (keyset.TitleKeys.TryGetValue(Header.RightsId, out byte[] titleKey))
|
IsMissingTitleKey = true;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
|
@ -58,36 +61,57 @@ 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
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 +128,8 @@ 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, IntegrityCheckLevel.None) ?? Stream.Null;
|
||||||
if (baseSect == 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,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);
|
Stream rawStream = OpenRawSection(index);
|
||||||
NcaSection sect = Sections[index];
|
|
||||||
|
|
||||||
if (raw || rawStream == null) return rawStream;
|
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:
|
case NcaHashType.Sha256:
|
||||||
return InitIvfcForPartitionfs(sect.Header.Sha256Info, new SharedStreamSource(rawStream), enableIntegrityChecks);
|
return InitIvfcForPartitionfs(header.Sha256Info, new SharedStreamSource(rawStream), integrityCheckLevel);
|
||||||
case SectionType.Romfs:
|
case NcaHashType.Ivfc:
|
||||||
case SectionType.Bktr:
|
return InitIvfcForRomfs(header.IvfcInfo, new SharedStreamSource(rawStream), integrityCheckLevel);
|
||||||
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"/> 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,
|
private static HierarchicalIntegrityVerificationStream InitIvfcForRomfs(IvfcHeader ivfc,
|
||||||
SharedStreamSource romfsStreamSource, bool enableIntegrityChecks)
|
SharedStreamSource romfsStreamSource, IntegrityCheckLevel integrityCheckLevel)
|
||||||
{
|
{
|
||||||
var initInfo = new IntegrityVerificationInfo[ivfc.NumLevels];
|
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,
|
private static Stream InitIvfcForPartitionfs(Sha256Info sb,
|
||||||
SharedStreamSource pfsStreamSource, bool enableIntegrityChecks)
|
SharedStreamSource pfsStreamSource, IntegrityCheckLevel integrityCheckLevel)
|
||||||
{
|
{
|
||||||
SharedStream hashStream = pfsStreamSource.CreateStream(sb.HashTableOffset, sb.HashTableSize);
|
SharedStream hashStream = pfsStreamSource.CreateStream(sb.HashTableOffset, sb.HashTableSize);
|
||||||
SharedStream dataStream = pfsStreamSource.CreateStream(sb.DataOffset, sb.DataSize);
|
SharedStream dataStream = pfsStreamSource.CreateStream(sb.DataOffset, sb.DataSize);
|
||||||
|
@ -192,13 +252,34 @@ namespace LibHac
|
||||||
Type = IntegrityStreamType.PartitionFs
|
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;
|
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,73 +339,49 @@ 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;
|
||||||
|
|
||||||
switch (sect.Type)
|
switch (sect.Header.HashType)
|
||||||
{
|
{
|
||||||
case SectionType.Invalid:
|
case NcaHashType.Sha256:
|
||||||
|
offset = sect.Header.Sha256Info.HashTableOffset;
|
||||||
|
size = sect.Header.Sha256Info.HashTableSize;
|
||||||
break;
|
break;
|
||||||
case SectionType.Pfs0:
|
case NcaHashType.Ivfc when sect.Header.EncryptionType == NcaEncryptionType.AesCtrEx:
|
||||||
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:
|
|
||||||
CheckBktrKey(sect);
|
CheckBktrKey(sect);
|
||||||
return;
|
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);
|
Stream stream = OpenSection(index, true, IntegrityCheckLevel.None);
|
||||||
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;
|
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()
|
public void Dispose()
|
||||||
|
@ -333,19 +400,34 @@ 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 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
|
||||||
{
|
{
|
||||||
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 (index < 0 || index > 3) throw new IndexOutOfRangeException();
|
||||||
if (nca.Sections[index] == null) return;
|
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);
|
string dir = Path.GetDirectoryName(filename);
|
||||||
if (!string.IsNullOrWhiteSpace(dir)) Directory.CreateDirectory(dir);
|
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 (index < 0 || index > 3) throw new IndexOutOfRangeException();
|
||||||
if (nca.Sections[index] == null) return;
|
if (nca.Sections[index] == null) return;
|
||||||
|
|
||||||
NcaSection section = nca.Sections[index];
|
NcaSection section = nca.Sections[index];
|
||||||
Stream stream = nca.OpenSection(index, false, verify);
|
Stream stream = nca.OpenSection(index, false, integrityCheckLevel);
|
||||||
|
|
||||||
switch (section.Type)
|
switch (section.Type)
|
||||||
{
|
{
|
||||||
|
@ -379,5 +461,53 @@ namespace LibHac
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 class BktrPatchInfo
|
||||||
{
|
{
|
||||||
public BktrHeader RelocationHeader;
|
public BktrHeader RelocationHeader;
|
||||||
|
@ -215,6 +189,8 @@ namespace LibHac
|
||||||
public int BlockSizePower;
|
public int BlockSizePower;
|
||||||
public uint Reserved;
|
public uint Reserved;
|
||||||
|
|
||||||
|
public Validity HashValidity = Validity.Unchecked;
|
||||||
|
|
||||||
public IvfcLevelHeader(BinaryReader reader)
|
public IvfcLevelHeader(BinaryReader reader)
|
||||||
{
|
{
|
||||||
LogicalOffset = reader.ReadInt64();
|
LogicalOffset = reader.ReadInt64();
|
||||||
|
@ -234,6 +210,8 @@ namespace LibHac
|
||||||
public long DataOffset;
|
public long DataOffset;
|
||||||
public long DataSize;
|
public long DataSize;
|
||||||
|
|
||||||
|
public Validity HashValidity = Validity.Unchecked;
|
||||||
|
|
||||||
public Sha256Info(BinaryReader reader)
|
public Sha256Info(BinaryReader reader)
|
||||||
{
|
{
|
||||||
MasterHash = reader.ReadBytes(0x20);
|
MasterHash = reader.ReadBytes(0x20);
|
||||||
|
@ -300,17 +278,12 @@ namespace LibHac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Pfs0Section
|
public enum ProgramPartitionType
|
||||||
{
|
{
|
||||||
public PfsSuperblock Superblock { get; set; }
|
Code,
|
||||||
public Validity Validity { get; set; }
|
Data,
|
||||||
}
|
Logo
|
||||||
|
};
|
||||||
public class RomfsSection
|
|
||||||
{
|
|
||||||
public RomfsSuperblock Superblock { get; set; }
|
|
||||||
public IvfcLevel[] IvfcLevels { get; set; } = new IvfcLevel[Romfs.IvfcMaxLevel];
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ContentType
|
public enum ContentType
|
||||||
{
|
{
|
||||||
|
@ -359,10 +332,11 @@ namespace LibHac
|
||||||
Bktr
|
Bktr
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Validity
|
public enum Validity : byte
|
||||||
{
|
{
|
||||||
Unchecked,
|
Unchecked,
|
||||||
Invalid,
|
Invalid,
|
||||||
Valid
|
Valid,
|
||||||
|
MissingKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,29 +68,6 @@ namespace LibHac
|
||||||
Hfs0
|
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 class PfsHeader
|
||||||
{
|
{
|
||||||
public string Magic;
|
public string Magic;
|
||||||
|
|
|
@ -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 class RomfsExtensions
|
||||||
{
|
{
|
||||||
public static void Extract(this Romfs romfs, string outDir, IProgressReport logger = null)
|
public static void Extract(this Romfs romfs, string outDir, IProgressReport logger = null)
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace LibHac.Savefile
|
||||||
public DirectoryEntry[] Directories { get; private set; }
|
public DirectoryEntry[] Directories { get; private set; }
|
||||||
private Dictionary<string, FileEntry> FileDict { get; }
|
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);
|
SavefileSource = new SharedStreamSource(file);
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ namespace LibHac.Savefile
|
||||||
JournalStream = new JournalStream(journalData, journalMap, (int)Header.Journal.BlockSize);
|
JournalStream = new JournalStream(journalData, journalMap, (int)Header.Journal.BlockSize);
|
||||||
JournalStreamSource = new SharedStreamSource(JournalStream);
|
JournalStreamSource = new SharedStreamSource(JournalStream);
|
||||||
|
|
||||||
IvfcStream = InitIvfcStream(enableIntegrityChecks);
|
IvfcStream = InitIvfcStream(integrityCheckLevel);
|
||||||
IvfcStreamSource = new SharedStreamSource(IvfcStream);
|
IvfcStreamSource = new SharedStreamSource(IvfcStream);
|
||||||
|
|
||||||
ReadFileInfo();
|
ReadFileInfo();
|
||||||
|
@ -120,7 +120,7 @@ namespace LibHac.Savefile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private HierarchicalIntegrityVerificationStream InitIvfcStream(bool enableIntegrityChecks)
|
private HierarchicalIntegrityVerificationStream InitIvfcStream(IntegrityCheckLevel integrityCheckLevel)
|
||||||
{
|
{
|
||||||
IvfcHeader ivfc = Header.Ivfc;
|
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)
|
public Stream OpenFile(string filename)
|
||||||
|
|
|
@ -83,7 +83,7 @@ namespace LibHac.Streams
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
while (idx+1 < _streamsStartPos.Count)
|
while (idx + 1 < _streamsStartPos.Count)
|
||||||
{
|
{
|
||||||
if (_streamsStartPos[idx + 1] > pos)
|
if (_streamsStartPos[idx + 1] > pos)
|
||||||
{
|
{
|
||||||
|
@ -122,10 +122,11 @@ namespace LibHac.Streams
|
||||||
|
|
||||||
if (count > 0)
|
if (count > 0)
|
||||||
{
|
{
|
||||||
if (_currentStreamIndex >= _streams.Count)
|
if (_currentStreamIndex + 1 >= _streams.Count)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
_currentStream = _streams[_currentStreamIndex++];
|
_currentStream = _streams[_currentStreamIndex + 1];
|
||||||
|
_currentStreamIndex++;
|
||||||
_currentStream.Position = 0;
|
_currentStream.Position = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,11 @@ namespace LibHac.Streams
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SectorSize { get; }
|
public int SectorSize { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of sectors in the stream.
|
||||||
|
/// </summary>
|
||||||
|
public int SectorCount { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum number of sectors that can be read or written in a single operation.
|
/// The maximum number of sectors that can be read or written in a single operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -64,6 +69,8 @@ namespace LibHac.Streams
|
||||||
_keepOpen = keepOpen;
|
_keepOpen = keepOpen;
|
||||||
_maxBufferSize = MaxSectors * SectorSize;
|
_maxBufferSize = MaxSectors * SectorSize;
|
||||||
baseStream.Position = offset;
|
baseStream.Position = offset;
|
||||||
|
|
||||||
|
SectorCount = (int)Util.DivideByRoundUp(_baseStream.Length - _offset, sectorSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Flush()
|
public override void Flush()
|
||||||
|
|
|
@ -126,7 +126,7 @@ namespace LibHac
|
||||||
|
|
||||||
string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/');
|
string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/');
|
||||||
var nax0 = new Nax0(Keyset, stream, sdPath, false);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -147,7 +147,7 @@ namespace LibHac
|
||||||
var title = new Title();
|
var title = new Title();
|
||||||
|
|
||||||
// Meta contents always have 1 Partition FS section with 1 file in it
|
// 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);
|
var pfs0 = new Pfs(sect);
|
||||||
Stream file = pfs0.OpenFile(pfs0.Files[0]);
|
Stream file = pfs0.OpenFile(pfs0.Files[0]);
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
foreach (Title title in Titles.Values.Where(x => x.ControlNca != null))
|
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");
|
byte[] control = romfs.GetFile("/control.nacp");
|
||||||
|
|
||||||
var reader = new BinaryReader(new MemoryStream(control));
|
var reader = new BinaryReader(new MemoryStream(control));
|
||||||
|
|
|
@ -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[]>
|
||||||
|
|
|
@ -102,7 +102,7 @@ namespace NandReader
|
||||||
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
|
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
|
||||||
{
|
{
|
||||||
var tickets = new List<Ticket>();
|
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 ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin"));
|
||||||
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin"));
|
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin"));
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ namespace NandReaderGui.ViewModel
|
||||||
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
|
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
|
||||||
{
|
{
|
||||||
var tickets = new List<Ticket>();
|
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 ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin"));
|
||||||
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin"));
|
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin"));
|
||||||
|
|
||||||
|
|
10
README.md
10
README.md
|
@ -15,9 +15,10 @@ hactoolnet is an example program that uses LibHac. It is used in a similar manne
|
||||||
Usage: hactoolnet.exe [options...] <path>
|
Usage: hactoolnet.exe [options...] <path>
|
||||||
Options:
|
Options:
|
||||||
-r, --raw Keep raw data, don't unpack.
|
-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.
|
-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.
|
--titlekeys <file> Load title keys from an external file.
|
||||||
NCA options:
|
NCA options:
|
||||||
--section0 <file> Specify Section 0 file path.
|
--section0 <file> Specify Section 0 file path.
|
||||||
|
@ -53,6 +54,8 @@ Package1 options:
|
||||||
--outdir <dir> Specify Package1 directory path.
|
--outdir <dir> Specify Package1 directory path.
|
||||||
Package2 options:
|
Package2 options:
|
||||||
--outdir <dir> Specify Package2 directory path.
|
--outdir <dir> Specify Package2 directory path.
|
||||||
|
INI1 options:
|
||||||
|
--outdir <dir> Specify INI1 directory path.
|
||||||
Switch FS options:
|
Switch FS options:
|
||||||
--sdseed <seed> Set console unique seed for SD card NAX0 encryption.
|
--sdseed <seed> Set console unique seed for SD card NAX0 encryption.
|
||||||
--listapps List application info.
|
--listapps List application info.
|
||||||
|
@ -64,10 +67,13 @@ Switch FS options:
|
||||||
--romfs <file> Specify RomFS directory path. (--title must be specified)
|
--romfs <file> Specify RomFS directory path. (--title must be specified)
|
||||||
--romfsdir <dir> 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.
|
--savedir <dir> Specify save file directory path.
|
||||||
|
-y, --verify Verify all titles, or verify a single title if --title is set.
|
||||||
Savefile options:
|
Savefile options:
|
||||||
--outdir <dir> Specify directory path to save contents to.
|
--outdir <dir> Specify directory path to save contents to.
|
||||||
--debugoutdir <dir> Specify directory path to save intermediate data to for debugging.
|
--debugoutdir <dir> Specify directory path to save intermediate data to for debugging.
|
||||||
--sign Sign the save file. (Requires device_key in key file)
|
--sign Sign the save file. (Requires device_key in key file)
|
||||||
|
Keygen options:
|
||||||
|
--outdir <dir> Specify directory path to save key files to.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
|
@ -198,6 +198,7 @@ namespace hactoolnet
|
||||||
sb.AppendLine(" --romfs <file> Specify RomFS directory path. (--title must be specified)");
|
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(" --romfsdir <dir> Specify RomFS directory path. (--title must be specified)");
|
||||||
sb.AppendLine(" --savedir <dir> Specify save file directory path.");
|
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("Savefile options:");
|
||||||
sb.AppendLine(" --outdir <dir> Specify directory path to save contents to.");
|
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.");
|
sb.AppendLine(" --debugoutdir <dir> Specify directory path to save intermediate data to for debugging.");
|
||||||
|
|
|
@ -36,6 +36,9 @@ namespace hactoolnet
|
||||||
public bool ListRomFs;
|
public bool ListRomFs;
|
||||||
public bool SignSave;
|
public bool SignSave;
|
||||||
public ulong TitleId;
|
public ulong TitleId;
|
||||||
|
|
||||||
|
public IntegrityCheckLevel IntegrityLevel =>
|
||||||
|
EnableHash ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum FileType
|
internal enum FileType
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -25,12 +26,12 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
if (ctx.Options.SectionOut[i] != null)
|
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)
|
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)
|
if (ctx.Options.Validate && nca.Sections[i] != null)
|
||||||
|
@ -41,7 +42,7 @@ namespace hactoolnet
|
||||||
|
|
||||||
if (ctx.Options.ListRomFs && nca.Sections[1] != null)
|
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)
|
foreach (RomfsFile romfsFile in romfs.Files)
|
||||||
{
|
{
|
||||||
|
@ -67,19 +68,25 @@ namespace hactoolnet
|
||||||
|
|
||||||
if (ctx.Options.RomfsOut != null)
|
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)
|
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);
|
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null)
|
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)
|
if (section == null)
|
||||||
{
|
{
|
||||||
|
@ -89,12 +96,12 @@ namespace hactoolnet
|
||||||
|
|
||||||
if (ctx.Options.ExefsOut != null)
|
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)
|
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];
|
NcaSection sect = nca.Sections[i];
|
||||||
if (sect == null) continue;
|
if (sect == null) continue;
|
||||||
|
|
||||||
|
bool isExefs = nca.Header.ContentType == ContentType.Program && i == (int)ProgramPartitionType.Code;
|
||||||
|
|
||||||
sb.AppendLine($" Section {i}:");
|
sb.AppendLine($" Section {i}:");
|
||||||
PrintItem(sb, colLen, " Offset:", $"0x{sect.Offset:x12}");
|
PrintItem(sb, colLen, " Offset:", $"0x{sect.Offset:x12}");
|
||||||
PrintItem(sb, colLen, " Size:", $"0x{sect.Size: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);
|
PrintItem(sb, colLen, " Section CTR:", sect.Header.Ctr);
|
||||||
|
|
||||||
switch (sect.Type)
|
switch (sect.Header.HashType)
|
||||||
{
|
{
|
||||||
case SectionType.Pfs0:
|
case NcaHashType.Sha256:
|
||||||
PrintPfs0(sect);
|
PrintSha256Hash(sect);
|
||||||
break;
|
break;
|
||||||
case SectionType.Romfs:
|
case NcaHashType.Ivfc:
|
||||||
PrintRomfs(sect);
|
PrintIvfcHash(sect);
|
||||||
break;
|
|
||||||
case SectionType.Bktr:
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
sb.AppendLine(" Unknown/invalid superblock!");
|
sb.AppendLine(" Unknown/invalid superblock!");
|
||||||
|
@ -176,13 +183,12 @@ namespace hactoolnet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintPfs0(NcaSection sect)
|
void PrintSha256Hash(NcaSection sect)
|
||||||
{
|
{
|
||||||
Sha256Info hashInfo = sect.Header.Sha256Info;
|
Sha256Info hashInfo = sect.Header.Sha256Info;
|
||||||
|
|
||||||
PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", hashInfo.MasterHash);
|
PrintItem(sb, colLen, $" Master Hash{sect.MasterHashValidity.GetValidityString()}:", hashInfo.MasterHash);
|
||||||
// todo sb.AppendLine($" Hash Table{sect.Pfs0.Validity.GetValidityString()}:");
|
sb.AppendLine($" Hash Table{sect.Header.Sha256Info.HashValidity.GetValidityString()}:");
|
||||||
sb.AppendLine($" Hash Table:");
|
|
||||||
|
|
||||||
PrintItem(sb, colLen, " Offset:", $"0x{hashInfo.HashTableOffset:x12}");
|
PrintItem(sb, colLen, " Offset:", $"0x{hashInfo.HashTableOffset:x12}");
|
||||||
PrintItem(sb, colLen, " Size:", $"0x{hashInfo.HashTableSize:x12}");
|
PrintItem(sb, colLen, " Size:", $"0x{hashInfo.HashTableSize:x12}");
|
||||||
|
@ -191,11 +197,11 @@ namespace hactoolnet
|
||||||
PrintItem(sb, colLen, " PFS0 Size:", $"0x{hashInfo.DataSize:x12}");
|
PrintItem(sb, colLen, " PFS0 Size:", $"0x{hashInfo.DataSize:x12}");
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintRomfs(NcaSection sect)
|
void PrintIvfcHash(NcaSection sect)
|
||||||
{
|
{
|
||||||
IvfcHeader ivfcInfo = sect.Header.IvfcInfo;
|
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, " Magic:", ivfcInfo.Magic);
|
||||||
PrintItem(sb, colLen, " Version:", $"{ivfcInfo.Version:x8}");
|
PrintItem(sb, colLen, " Version:", $"{ivfcInfo.Version:x8}");
|
||||||
|
|
||||||
|
@ -209,8 +215,7 @@ namespace hactoolnet
|
||||||
hashOffset = ivfcInfo.LevelHeaders[i - 1].LogicalOffset;
|
hashOffset = ivfcInfo.LevelHeaders[i - 1].LogicalOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:");
|
sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:");
|
||||||
sb.AppendLine($" Level {i}:");
|
|
||||||
PrintItem(sb, colLen, " Data Offset:", $"0x{level.LogicalOffset:x12}");
|
PrintItem(sb, colLen, " Data Offset:", $"0x{level.LogicalOffset:x12}");
|
||||||
PrintItem(sb, colLen, " Data Size:", $"0x{level.HashDataSize:x12}");
|
PrintItem(sb, colLen, " Data Size:", $"0x{level.HashDataSize:x12}");
|
||||||
PrintItem(sb, colLen, " Hash Offset:", $"0x{hashOffset:x12}");
|
PrintItem(sb, colLen, " Hash Offset:", $"0x{hashOffset:x12}");
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.ReadWrite))
|
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)
|
if (ctx.Options.OutDir != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace hactoolnet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NcaSection section = title.MainNca.Sections.FirstOrDefault(x => x.IsExefs);
|
NcaSection section = title.MainNca.Sections[(int)ProgramPartitionType.Code];
|
||||||
|
|
||||||
if (section == null)
|
if (section == null)
|
||||||
{
|
{
|
||||||
|
@ -55,12 +55,12 @@ namespace hactoolnet
|
||||||
|
|
||||||
if (ctx.Options.ExefsOutDir != null)
|
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)
|
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)
|
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);
|
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.Options.RomfsOut != null)
|
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);
|
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)
|
private static void SaveTitle(Context ctx, SwitchFs switchFs)
|
||||||
|
@ -170,7 +231,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}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ namespace hactoolnet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NcaSection exefsSection = mainNca.Sections.FirstOrDefault(x => x.IsExefs);
|
NcaSection exefsSection = mainNca.Sections[(int)ProgramPartitionType.Code];
|
||||||
|
|
||||||
if (exefsSection == null)
|
if (exefsSection == null)
|
||||||
{
|
{
|
||||||
|
@ -79,12 +79,12 @@ namespace hactoolnet
|
||||||
|
|
||||||
if (ctx.Options.ExefsOutDir != null)
|
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)
|
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)
|
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);
|
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.Options.RomfsOut != null)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,47 +46,52 @@ namespace hactoolnet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (ctx.Options.InFileType)
|
RunTask(ctx);
|
||||||
{
|
}
|
||||||
case FileType.Nca:
|
}
|
||||||
ProcessNca.Process(ctx);
|
|
||||||
break;
|
private static void RunTask(Context ctx)
|
||||||
case FileType.Pfs0:
|
{
|
||||||
case FileType.Nsp:
|
switch (ctx.Options.InFileType)
|
||||||
ProcessNsp.Process(ctx);
|
{
|
||||||
break;
|
case FileType.Nca:
|
||||||
case FileType.Romfs:
|
ProcessNca.Process(ctx);
|
||||||
ProcessRomfs.Process(ctx);
|
break;
|
||||||
break;
|
case FileType.Pfs0:
|
||||||
case FileType.Nax0:
|
case FileType.Nsp:
|
||||||
break;
|
ProcessNsp.Process(ctx);
|
||||||
case FileType.SwitchFs:
|
break;
|
||||||
ProcessSwitchFs.Process(ctx);
|
case FileType.Romfs:
|
||||||
break;
|
ProcessRomfs.Process(ctx);
|
||||||
case FileType.Save:
|
break;
|
||||||
ProcessSave.Process(ctx);
|
case FileType.Nax0:
|
||||||
break;
|
break;
|
||||||
case FileType.Xci:
|
case FileType.SwitchFs:
|
||||||
ProcessXci.Process(ctx);
|
ProcessSwitchFs.Process(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Keygen:
|
case FileType.Save:
|
||||||
ProcessKeygen(ctx);
|
ProcessSave.Process(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Pk11:
|
case FileType.Xci:
|
||||||
ProcessPackage.ProcessPk11(ctx);
|
ProcessXci.Process(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Pk21:
|
case FileType.Keygen:
|
||||||
ProcessPackage.ProcessPk21(ctx);
|
ProcessKeygen(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Kip1:
|
case FileType.Pk11:
|
||||||
ProcessKip.ProcessKip1(ctx);
|
ProcessPackage.ProcessPk11(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Ini1:
|
case FileType.Pk21:
|
||||||
ProcessKip.ProcessIni1(ctx);
|
ProcessPackage.ProcessPk21(ctx);
|
||||||
break;
|
break;
|
||||||
default:
|
case FileType.Kip1:
|
||||||
throw new ArgumentOutOfRangeException();
|
ProcessKip.ProcessKip1(ctx);
|
||||||
}
|
break;
|
||||||
|
case FileType.Ini1:
|
||||||
|
ProcessKip.ProcessIni1(ctx);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue