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));
|
Counter[8] = (byte)((Counter[8] & 0xF0) | (int)(off & 0x0F));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
{
|
{
|
||||||
switch (origin)
|
switch (origin)
|
||||||
|
@ -130,4 +130,32 @@ namespace LibHac
|
|||||||
set => DataLevel.Position = value;
|
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())
|
if (Type == IntegrityStreamType.Save && _hashBuffer.IsEmpty())
|
||||||
{
|
{
|
||||||
Array.Clear(buffer, offset, SectorSize);
|
Array.Clear(buffer, offset, SectorSize);
|
||||||
|
BlockValidities[blockNum] = Validity.Valid;
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,7 +381,6 @@ namespace LibHac
|
|||||||
stream.Read(hashTable, 0, hashTable.Length);
|
stream.Read(hashTable, 0, hashTable.Length);
|
||||||
|
|
||||||
sect.MasterHashValidity = Crypto.CheckMemoryHashTable(hashTable, expected, 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()
|
public void Dispose()
|
||||||
@ -400,7 +399,21 @@ 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 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()
|
public byte[] GetMasterHash()
|
||||||
{
|
{
|
||||||
@ -493,7 +506,7 @@ namespace LibHac
|
|||||||
|
|
||||||
if (hashType == NcaHashType.Ivfc)
|
if (hashType == NcaHashType.Ivfc)
|
||||||
{
|
{
|
||||||
SetIvfcLevelValidities(stream, sect.Header.IvfcInfo);
|
stream.SetLevelValidities(sect.Header.IvfcInfo);
|
||||||
}
|
}
|
||||||
else if (hashType == NcaHashType.Sha256)
|
else if (hashType == NcaHashType.Sha256)
|
||||||
{
|
{
|
||||||
@ -502,30 +515,5 @@ namespace LibHac
|
|||||||
|
|
||||||
return validity;
|
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 string Magic;
|
||||||
public int Version;
|
public int Version;
|
||||||
public uint MasterHashSize;
|
public int MasterHashSize;
|
||||||
public uint NumLevels;
|
public int NumLevels;
|
||||||
public IvfcLevelHeader[] LevelHeaders = new IvfcLevelHeader[6];
|
public IvfcLevelHeader[] LevelHeaders = new IvfcLevelHeader[6];
|
||||||
public byte[] SaltSource;
|
public byte[] SaltSource;
|
||||||
public byte[] MasterHash;
|
public byte[] MasterHash;
|
||||||
@ -167,10 +167,10 @@ namespace LibHac
|
|||||||
public IvfcHeader(BinaryReader reader)
|
public IvfcHeader(BinaryReader reader)
|
||||||
{
|
{
|
||||||
Magic = reader.ReadAscii(4);
|
Magic = reader.ReadAscii(4);
|
||||||
Version = reader.ReadInt16();
|
|
||||||
reader.BaseStream.Position += 2;
|
reader.BaseStream.Position += 2;
|
||||||
MasterHashSize = reader.ReadUInt32();
|
Version = reader.ReadInt16();
|
||||||
NumLevels = reader.ReadUInt32();
|
MasterHashSize = reader.ReadInt32();
|
||||||
|
NumLevels = reader.ReadInt32();
|
||||||
|
|
||||||
for (int i = 0; i < LevelHeaders.Length; i++)
|
for (int i = 0; i < LevelHeaders.Length; i++)
|
||||||
{
|
{
|
||||||
@ -210,6 +210,7 @@ namespace LibHac
|
|||||||
public long DataOffset;
|
public long DataOffset;
|
||||||
public long DataSize;
|
public long DataSize;
|
||||||
|
|
||||||
|
public Validity MasterHashValidity = Validity.Unchecked;
|
||||||
public Validity HashValidity = Validity.Unchecked;
|
public Validity HashValidity = Validity.Unchecked;
|
||||||
|
|
||||||
public Sha256Info(BinaryReader reader)
|
public Sha256Info(BinaryReader reader)
|
||||||
|
@ -151,6 +151,14 @@ namespace LibHac.Save
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Validity Verify(IProgressReport logger = null)
|
||||||
|
{
|
||||||
|
Validity validity = IvfcStream.Validate(true, logger);
|
||||||
|
IvfcStream.SetLevelValidities(Header.Ivfc);
|
||||||
|
|
||||||
|
return validity;
|
||||||
|
}
|
||||||
|
|
||||||
private string[] SaltSources =
|
private string[] SaltSources =
|
||||||
{
|
{
|
||||||
"HierarchicalIntegrityVerificationStorage::Master",
|
"HierarchicalIntegrityVerificationStorage::Master",
|
||||||
|
@ -44,6 +44,7 @@ namespace hactoolnet
|
|||||||
new CliOption("listapps", 0, (o, a) => o.ListApps = true),
|
new CliOption("listapps", 0, (o, a) => o.ListApps = true),
|
||||||
new CliOption("listtitles", 0, (o, a) => o.ListTitles = true),
|
new CliOption("listtitles", 0, (o, a) => o.ListTitles = true),
|
||||||
new CliOption("listromfs", 0, (o, a) => o.ListRomFs = 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("sign", 0, (o, a) => o.SignSave = true),
|
||||||
new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])),
|
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(" --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.");
|
||||||
sb.AppendLine(" --sign Sign the save file. (Requires device_key in key file)");
|
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("Keygen options:");
|
||||||
sb.AppendLine(" --outdir <dir> Specify directory path to save key files to.");
|
sb.AppendLine(" --outdir <dir> Specify directory path to save key files to.");
|
||||||
|
|
||||||
|
@ -34,11 +34,19 @@ namespace hactoolnet
|
|||||||
public bool ListApps;
|
public bool ListApps;
|
||||||
public bool ListTitles;
|
public bool ListTitles;
|
||||||
public bool ListRomFs;
|
public bool ListRomFs;
|
||||||
|
public bool ListFiles;
|
||||||
public bool SignSave;
|
public bool SignSave;
|
||||||
public ulong TitleId;
|
public ulong TitleId;
|
||||||
|
|
||||||
public IntegrityCheckLevel IntegrityLevel =>
|
public IntegrityCheckLevel IntegrityLevel
|
||||||
EnableHash ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Validate) return IntegrityCheckLevel.IgnoreOnInvalid;
|
||||||
|
if (EnableHash) return IntegrityCheckLevel.ErrorOnInvalid;
|
||||||
|
return IntegrityCheckLevel.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum FileType
|
internal enum FileType
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System;
|
||||||
|
using System.Text;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
|
|
||||||
namespace hactoolnet
|
namespace hactoolnet
|
||||||
@ -26,5 +27,42 @@ namespace hactoolnet
|
|||||||
default: return string.Empty;
|
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);
|
PrintSha256Hash(sect);
|
||||||
break;
|
break;
|
||||||
case NcaHashType.Ivfc:
|
case NcaHashType.Ivfc:
|
||||||
PrintIvfcHash(sect);
|
PrintIvfcHash(sb, colLen, 8, sect.Header.IvfcInfo, IntegrityStreamType.RomFs);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
sb.AppendLine(" Unknown/invalid superblock!");
|
sb.AppendLine(" Unknown/invalid superblock!");
|
||||||
@ -196,32 +196,6 @@ namespace hactoolnet
|
|||||||
PrintItem(sb, colLen, " PFS0 Offset:", $"0x{hashInfo.DataOffset:x12}");
|
PrintItem(sb, colLen, " PFS0 Offset:", $"0x{hashInfo.DataOffset:x12}");
|
||||||
PrintItem(sb, colLen, " PFS0 Size:", $"0x{hashInfo.DataSize: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;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
using LibHac.Save;
|
using LibHac.Save;
|
||||||
@ -16,6 +15,11 @@ namespace hactoolnet
|
|||||||
{
|
{
|
||||||
var save = new Savefile(ctx.Keyset, file, ctx.Options.IntegrityLevel);
|
var save = new Savefile(ctx.Keyset, file, ctx.Options.IntegrityLevel);
|
||||||
|
|
||||||
|
if (ctx.Options.Validate)
|
||||||
|
{
|
||||||
|
save.Verify(ctx.Logger);
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx.Options.OutDir != null)
|
if (ctx.Options.OutDir != null)
|
||||||
{
|
{
|
||||||
save.Extract(ctx.Options.OutDir, ctx.Logger);
|
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());
|
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, "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, "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, $"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);
|
PrintItem(sb, colLen, "Number of Files:", save.Files.Length);
|
||||||
|
|
||||||
if (save.Files.Length > 0 && save.Files.Length < 100)
|
PrintIvfcHash(sb, colLen, 4, save.Header.Ivfc, IntegrityStreamType.Save);
|
||||||
{
|
|
||||||
sb.AppendLine("Files:");
|
|
||||||
foreach (FileEntry file in save.Files.OrderBy(x => x.FullPath))
|
|
||||||
{
|
|
||||||
sb.AppendLine(file.FullPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user