From f83a284b966a0288ad9dc99bb466a3eea1405acf Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Thu, 18 Oct 2018 17:54:24 -0500 Subject: [PATCH] Add verification option to save files --- LibHac/Aes128CtrStream.cs | 5 --- ...HierarchicalIntegrityVerificationStream.cs | 28 ++++++++++++ LibHac/IntegrityVerificationStream.cs | 1 + LibHac/Nca.cs | 44 +++++++------------ LibHac/NcaStructs.cs | 13 +++--- LibHac/Save/Savefile.cs | 8 ++++ hactoolnet/CliParser.cs | 4 +- hactoolnet/Options.cs | 12 ++++- hactoolnet/Print.cs | 40 ++++++++++++++++- hactoolnet/ProcessNca.cs | 28 +----------- hactoolnet/ProcessSave.cs | 24 +++++----- 11 files changed, 127 insertions(+), 80 deletions(-) diff --git a/LibHac/Aes128CtrStream.cs b/LibHac/Aes128CtrStream.cs index 382251b2..9c48c2a1 100644 --- a/LibHac/Aes128CtrStream.cs +++ b/LibHac/Aes128CtrStream.cs @@ -105,11 +105,6 @@ namespace LibHac Counter[8] = (byte)((Counter[8] & 0xF0) | (int)(off & 0x0F)); } - public override void Flush() - { - throw new NotImplementedException(); - } - public override long Seek(long offset, SeekOrigin origin) { switch (origin) diff --git a/LibHac/HierarchicalIntegrityVerificationStream.cs b/LibHac/HierarchicalIntegrityVerificationStream.cs index 64c0f5f2..dafed09e 100644 --- a/LibHac/HierarchicalIntegrityVerificationStream.cs +++ b/LibHac/HierarchicalIntegrityVerificationStream.cs @@ -130,4 +130,32 @@ namespace LibHac set => DataLevel.Position = value; } } + + public static class HierarchicalIntegrityVerificationStreamExtensions + { + internal static void SetLevelValidities(this HierarchicalIntegrityVerificationStream stream, IvfcHeader header) + { + for (int i = 0; i < stream.Levels.Length - 1; i++) + { + Validity[] level = stream.LevelValidities[i]; + var levelValidity = Validity.Valid; + + foreach (Validity block in level) + { + if (block == Validity.Invalid) + { + levelValidity = Validity.Invalid; + break; + } + + if (block == Validity.Unchecked && levelValidity != Validity.Invalid) + { + levelValidity = Validity.Unchecked; + } + } + + header.LevelHeaders[i].HashValidity = levelValidity; + } + } + } } diff --git a/LibHac/IntegrityVerificationStream.cs b/LibHac/IntegrityVerificationStream.cs index 397a2c4b..65e73295 100644 --- a/LibHac/IntegrityVerificationStream.cs +++ b/LibHac/IntegrityVerificationStream.cs @@ -71,6 +71,7 @@ namespace LibHac if (Type == IntegrityStreamType.Save && _hashBuffer.IsEmpty()) { Array.Clear(buffer, offset, SectorSize); + BlockValidities[blockNum] = Validity.Valid; return bytesRead; } diff --git a/LibHac/Nca.cs b/LibHac/Nca.cs index a6eb1b17..77b4105b 100644 --- a/LibHac/Nca.cs +++ b/LibHac/Nca.cs @@ -381,7 +381,6 @@ namespace LibHac stream.Read(hashTable, 0, hashTable.Length); sect.MasterHashValidity = Crypto.CheckMemoryHashTable(hashTable, expected, 0, hashTable.Length); - if (sect.Header.HashType == NcaHashType.Ivfc) sect.Header.IvfcInfo.LevelHeaders[0].HashValidity = sect.MasterHashValidity; } public void Dispose() @@ -400,7 +399,21 @@ namespace LibHac public int SectionNum { get; set; } public long Offset { get; set; } public long Size { get; set; } - public Validity MasterHashValidity { get; set; } + + public Validity MasterHashValidity + { + get + { + if (Header.HashType == NcaHashType.Ivfc) return Header.IvfcInfo.LevelHeaders[0].HashValidity; + if (Header.HashType == NcaHashType.Sha256) return Header.Sha256Info.MasterHashValidity; + return Validity.Unchecked; + } + set + { + if (Header.HashType == NcaHashType.Ivfc) Header.IvfcInfo.LevelHeaders[0].HashValidity = value; + if (Header.HashType == NcaHashType.Sha256) Header.Sha256Info.MasterHashValidity = value; + } + } public byte[] GetMasterHash() { @@ -493,7 +506,7 @@ namespace LibHac if (hashType == NcaHashType.Ivfc) { - SetIvfcLevelValidities(stream, sect.Header.IvfcInfo); + stream.SetLevelValidities(sect.Header.IvfcInfo); } else if (hashType == NcaHashType.Sha256) { @@ -502,30 +515,5 @@ namespace LibHac return validity; } - - private static void SetIvfcLevelValidities(HierarchicalIntegrityVerificationStream stream, IvfcHeader header) - { - for (int i = 0; i < stream.Levels.Length - 1; i++) - { - Validity[] level = stream.LevelValidities[i]; - var levelValidity = Validity.Valid; - - foreach (Validity block in level) - { - if (block == Validity.Invalid) - { - levelValidity = Validity.Invalid; - break; - } - - if (block == Validity.Unchecked && levelValidity != Validity.Invalid) - { - levelValidity = Validity.Unchecked; - } - } - - header.LevelHeaders[i].HashValidity = levelValidity; - } - } } } diff --git a/LibHac/NcaStructs.cs b/LibHac/NcaStructs.cs index c75f1c68..9ed3c0ee 100644 --- a/LibHac/NcaStructs.cs +++ b/LibHac/NcaStructs.cs @@ -158,8 +158,8 @@ namespace LibHac { public string Magic; public int Version; - public uint MasterHashSize; - public uint NumLevels; + public int MasterHashSize; + public int NumLevels; public IvfcLevelHeader[] LevelHeaders = new IvfcLevelHeader[6]; public byte[] SaltSource; public byte[] MasterHash; @@ -167,10 +167,10 @@ namespace LibHac public IvfcHeader(BinaryReader reader) { Magic = reader.ReadAscii(4); - Version = reader.ReadInt16(); reader.BaseStream.Position += 2; - MasterHashSize = reader.ReadUInt32(); - NumLevels = reader.ReadUInt32(); + Version = reader.ReadInt16(); + MasterHashSize = reader.ReadInt32(); + NumLevels = reader.ReadInt32(); for (int i = 0; i < LevelHeaders.Length; i++) { @@ -210,8 +210,9 @@ namespace LibHac public long DataOffset; public long DataSize; + public Validity MasterHashValidity = Validity.Unchecked; public Validity HashValidity = Validity.Unchecked; - + public Sha256Info(BinaryReader reader) { MasterHash = reader.ReadBytes(0x20); diff --git a/LibHac/Save/Savefile.cs b/LibHac/Save/Savefile.cs index ba3394f0..3ab2f999 100644 --- a/LibHac/Save/Savefile.cs +++ b/LibHac/Save/Savefile.cs @@ -151,6 +151,14 @@ namespace LibHac.Save return true; } + public Validity Verify(IProgressReport logger = null) + { + Validity validity = IvfcStream.Validate(true, logger); + IvfcStream.SetLevelValidities(Header.Ivfc); + + return validity; + } + private string[] SaltSources = { "HierarchicalIntegrityVerificationStorage::Master", diff --git a/hactoolnet/CliParser.cs b/hactoolnet/CliParser.cs index d2a434d4..089eb094 100644 --- a/hactoolnet/CliParser.cs +++ b/hactoolnet/CliParser.cs @@ -44,6 +44,7 @@ namespace hactoolnet new CliOption("listapps", 0, (o, a) => o.ListApps = true), new CliOption("listtitles", 0, (o, a) => o.ListTitles = true), new CliOption("listromfs", 0, (o, a) => o.ListRomFs = true), + new CliOption("listfiles", 0, (o, a) => o.ListFiles = true), new CliOption("sign", 0, (o, a) => o.SignSave = true), new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])), }; @@ -203,9 +204,10 @@ namespace hactoolnet sb.AppendLine(" --outdir Specify directory path to save contents to."); sb.AppendLine(" --debugoutdir Specify directory path to save intermediate data to for debugging."); sb.AppendLine(" --sign Sign the save file. (Requires device_key in key file)"); + sb.AppendLine(" --listfiles List files in save file."); sb.AppendLine("Keygen options:"); sb.AppendLine(" --outdir Specify directory path to save key files to."); - + return sb.ToString(); } diff --git a/hactoolnet/Options.cs b/hactoolnet/Options.cs index 69d4aa30..2f8c77a9 100644 --- a/hactoolnet/Options.cs +++ b/hactoolnet/Options.cs @@ -34,11 +34,19 @@ namespace hactoolnet public bool ListApps; public bool ListTitles; public bool ListRomFs; + public bool ListFiles; public bool SignSave; public ulong TitleId; - public IntegrityCheckLevel IntegrityLevel => - EnableHash ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; + public IntegrityCheckLevel IntegrityLevel + { + get + { + if (Validate) return IntegrityCheckLevel.IgnoreOnInvalid; + if (EnableHash) return IntegrityCheckLevel.ErrorOnInvalid; + return IntegrityCheckLevel.None; + } + } } internal enum FileType diff --git a/hactoolnet/Print.cs b/hactoolnet/Print.cs index f9df4879..e0a1821d 100644 --- a/hactoolnet/Print.cs +++ b/hactoolnet/Print.cs @@ -1,4 +1,5 @@ -using System.Text; +using System; +using System.Text; using LibHac; namespace hactoolnet @@ -26,5 +27,42 @@ namespace hactoolnet default: return string.Empty; } } + + public static void PrintIvfcHash(StringBuilder sb, int colLen, int indentSize, IvfcHeader ivfcInfo, IntegrityStreamType type) + { + string prefix = new string(' ', indentSize); + string prefix2 = new string(' ', indentSize + 4); + + if (type == IntegrityStreamType.RomFs) + PrintItem(sb, colLen, $"{prefix}Master Hash{ivfcInfo.LevelHeaders[0].HashValidity.GetValidityString()}:", ivfcInfo.MasterHash); + + PrintItem(sb, colLen, $"{prefix}Magic:", ivfcInfo.Magic); + PrintItem(sb, colLen, $"{prefix}Version:", ivfcInfo.Version); + + if (type == IntegrityStreamType.Save) + PrintItem(sb, colLen, $"{prefix}Salt Seed:", ivfcInfo.SaltSource); + + int levelCount = Math.Max(ivfcInfo.NumLevels - 1, 0); + if (type == IntegrityStreamType.Save) levelCount = 4; + + int offsetLen = type == IntegrityStreamType.Save ? 16 : 12; + + for (int i = 0; i < levelCount; i++) + { + IvfcLevelHeader level = ivfcInfo.LevelHeaders[i]; + long hashOffset = 0; + + if (i != 0) + { + hashOffset = ivfcInfo.LevelHeaders[i - 1].LogicalOffset; + } + + sb.AppendLine($"{prefix}Level {i}{level.HashValidity.GetValidityString()}:"); + PrintItem(sb, colLen, $"{prefix2}Data Offset:", $"0x{level.LogicalOffset.ToString($"x{offsetLen}")}"); + PrintItem(sb, colLen, $"{prefix2}Data Size:", $"0x{level.HashDataSize.ToString($"x{offsetLen}")}"); + PrintItem(sb, colLen, $"{prefix2}Hash Offset:", $"0x{hashOffset.ToString($"x{offsetLen}")}"); + PrintItem(sb, colLen, $"{prefix2}Hash BlockSize:", $"0x{1 << level.BlockSizePower:x8}"); + } + } } } diff --git a/hactoolnet/ProcessNca.cs b/hactoolnet/ProcessNca.cs index 1b87a1e8..758d9ad7 100644 --- a/hactoolnet/ProcessNca.cs +++ b/hactoolnet/ProcessNca.cs @@ -174,7 +174,7 @@ namespace hactoolnet PrintSha256Hash(sect); break; case NcaHashType.Ivfc: - PrintIvfcHash(sect); + PrintIvfcHash(sb, colLen, 8, sect.Header.IvfcInfo, IntegrityStreamType.RomFs); break; default: sb.AppendLine(" Unknown/invalid superblock!"); @@ -196,32 +196,6 @@ namespace hactoolnet PrintItem(sb, colLen, " PFS0 Offset:", $"0x{hashInfo.DataOffset:x12}"); PrintItem(sb, colLen, " PFS0 Size:", $"0x{hashInfo.DataSize:x12}"); } - - void PrintIvfcHash(NcaSection sect) - { - IvfcHeader ivfcInfo = sect.Header.IvfcInfo; - - PrintItem(sb, colLen, $" Master Hash{sect.MasterHashValidity.GetValidityString()}:", ivfcInfo.MasterHash); - PrintItem(sb, colLen, " Magic:", ivfcInfo.Magic); - PrintItem(sb, colLen, " Version:", $"{ivfcInfo.Version:x8}"); - - for (int i = 0; i < Romfs.IvfcMaxLevel; i++) - { - IvfcLevelHeader level = ivfcInfo.LevelHeaders[i]; - long hashOffset = 0; - - if (i != 0) - { - hashOffset = ivfcInfo.LevelHeaders[i - 1].LogicalOffset; - } - - sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:"); - PrintItem(sb, colLen, " Data Offset:", $"0x{level.LogicalOffset:x12}"); - PrintItem(sb, colLen, " Data Size:", $"0x{level.HashDataSize:x12}"); - PrintItem(sb, colLen, " Hash Offset:", $"0x{hashOffset:x12}"); - PrintItem(sb, colLen, " Hash BlockSize:", $"0x{1 << level.BlockSizePower:x8}"); - } - } } } } diff --git a/hactoolnet/ProcessSave.cs b/hactoolnet/ProcessSave.cs index 3d5743ce..dce56572 100644 --- a/hactoolnet/ProcessSave.cs +++ b/hactoolnet/ProcessSave.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Text; using LibHac; using LibHac.Save; @@ -16,6 +15,11 @@ namespace hactoolnet { var save = new Savefile(ctx.Keyset, file, ctx.Options.IntegrityLevel); + if (ctx.Options.Validate) + { + save.Verify(ctx.Logger); + } + if (ctx.Options.OutDir != null) { save.Extract(ctx.Options.OutDir, ctx.Logger); @@ -79,6 +83,14 @@ namespace hactoolnet } } + if (ctx.Options.ListFiles) + { + foreach (FileEntry fileEntry in save.Files) + { + ctx.Logger.LogMessage(fileEntry.FullPath); + } + } + ctx.Logger.LogMessage(save.Print()); } } @@ -100,17 +112,9 @@ namespace hactoolnet PrintItem(sb, colLen, "Save Data Size:", $"0x{save.Header.ExtraData.DataSize:x16} ({Util.GetBytesReadable(save.Header.ExtraData.DataSize)})"); PrintItem(sb, colLen, "Journal Size:", $"0x{save.Header.ExtraData.JournalSize:x16} ({Util.GetBytesReadable(save.Header.ExtraData.JournalSize)})"); PrintItem(sb, colLen, $"Header Hash{save.Header.HeaderHashValidity.GetValidityString()}:", save.Header.Layout.Hash); - PrintItem(sb, colLen, "IVFC Salt Seed:", save.Header.Ivfc.SaltSource); PrintItem(sb, colLen, "Number of Files:", save.Files.Length); - if (save.Files.Length > 0 && save.Files.Length < 100) - { - sb.AppendLine("Files:"); - foreach (FileEntry file in save.Files.OrderBy(x => x.FullPath)) - { - sb.AppendLine(file.FullPath); - } - } + PrintIvfcHash(sb, colLen, 4, save.Header.Ivfc, IntegrityStreamType.Save); return sb.ToString(); }