mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Add verification option to save files
This commit is contained in:
parent
f8e7c00ef4
commit
f83a284b96
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +71,7 @@ namespace LibHac
|
||||
if (Type == IntegrityStreamType.Save && _hashBuffer.IsEmpty())
|
||||
{
|
||||
Array.Clear(buffer, offset, SectorSize);
|
||||
BlockValidities[blockNum] = Validity.Valid;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,6 +210,7 @@ namespace LibHac
|
||||
public long DataOffset;
|
||||
public long DataSize;
|
||||
|
||||
public Validity MasterHashValidity = Validity.Unchecked;
|
||||
public Validity HashValidity = Validity.Unchecked;
|
||||
|
||||
public Sha256Info(BinaryReader reader)
|
||||
|
@ -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",
|
||||
|
@ -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,6 +204,7 @@ namespace hactoolnet
|
||||
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(" --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 <dir> Specify directory path to save key files to.");
|
||||
|
||||
|
@ -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
|
||||
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user