From a21283bf440761376b9870fa78dc407c8125a551 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 1 Oct 2018 12:55:10 -0500 Subject: [PATCH] Print information about save files --- LibHac/Savefile/Header.cs | 151 ++++++++++++++++++++++++------------ LibHac/Savefile/Journal.cs | 3 - LibHac/Savefile/Savefile.cs | 10 +-- hactoolnet/ProcessSave.cs | 100 ++++++++++++++++++++++++ hactoolnet/Program.cs | 60 +------------- 5 files changed, 211 insertions(+), 113 deletions(-) create mode 100644 hactoolnet/ProcessSave.cs diff --git a/LibHac/Savefile/Header.cs b/LibHac/Savefile/Header.cs index 5512a55d..5f841805 100644 --- a/LibHac/Savefile/Header.cs +++ b/LibHac/Savefile/Header.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Security.Cryptography; namespace LibHac.Savefile @@ -14,6 +15,7 @@ namespace LibHac.Savefile public RemapHeader FileRemap { get; set; } public RemapHeader MetaRemap { get; set; } + public ExtraData ExtraData { get; set; } public MapEntry[] FileMapEntries { get; set; } public MapEntry[] MetaMapEntries { get; set; } @@ -56,15 +58,18 @@ namespace LibHac.Savefile reader.BaseStream.Position = 0x690; MetaRemap = new RemapHeader(reader); - reader.BaseStream.Position = Layout.MasterHashOffset0; - MasterHashA = reader.ReadBytes((int)Layout.MasterHashSize); - reader.BaseStream.Position = Layout.MasterHashOffset1; - MasterHashB = reader.ReadBytes((int)Layout.MasterHashSize); + reader.BaseStream.Position = 0x6D8; + ExtraData = new ExtraData(reader); - reader.BaseStream.Position = Layout.L1BitmapOffset0; - DuplexMasterA = reader.ReadBytes((int)Layout.L1BitmapSize); - reader.BaseStream.Position = Layout.L1BitmapOffset1; - DuplexMasterB = reader.ReadBytes((int)Layout.L1BitmapSize); + reader.BaseStream.Position = Layout.IvfcMasterHashOffsetA; + MasterHashA = reader.ReadBytes((int)Layout.IvfcMasterHashSize); + reader.BaseStream.Position = Layout.IvfcMasterHashOffsetB; + MasterHashB = reader.ReadBytes((int)Layout.IvfcMasterHashSize); + + reader.BaseStream.Position = Layout.DuplexMasterOffsetA; + DuplexMasterA = reader.ReadBytes((int)Layout.DuplexMasterSize); + reader.BaseStream.Position = Layout.DuplexMasterOffsetB; + DuplexMasterB = reader.ReadBytes((int)Layout.DuplexMasterSize); reader.BaseStream.Position = Layout.FileMapEntryOffset; FileMapEntries = new MapEntry[FileRemap.MapEntryCount]; @@ -82,8 +87,6 @@ namespace LibHac.Savefile HeaderHashValidity = ValidateHeaderHash(); SignatureValidity = ValidateSignature(keyset); - - logger?.LogMessage($"Header hash is {HeaderHashValidity}"); } private Validity ValidateHeaderHash() @@ -126,12 +129,12 @@ namespace LibHac.Savefile public long JournalDataSizeA { get; set; } public long JournalDataSizeB { get; set; } public long SizeReservedArea { get; set; } - public long L1BitmapOffset0 { get; set; } - public long L1BitmapOffset1 { get; set; } - public long L1BitmapSize { get; set; } - public long MasterHashOffset0 { get; set; } - public long MasterHashOffset1 { get; set; } - public long MasterHashSize { get; set; } + public long DuplexMasterOffsetA { get; set; } + public long DuplexMasterOffsetB { get; set; } + public long DuplexMasterSize { get; set; } + public long IvfcMasterHashOffsetA { get; set; } + public long IvfcMasterHashOffsetB { get; set; } + public long IvfcMasterHashSize { get; set; } public long JournalTableOffset { get; set; } public long JournalTableSize { get; set; } public long JournalBitmapUpdatedPhysicalOffset { get; set; } @@ -140,14 +143,14 @@ namespace LibHac.Savefile public long JournalBitmapUpdatedVirtualSize { get; set; } public long JournalBitmapUnassignedOffset { get; set; } public long JournalBitmapUnassignedSize { get; set; } - public long Layer1HashOffset { get; set; } - public long Layer1HashSize { get; set; } - public long Layer2HashOffset { get; set; } - public long Layer2HashSize { get; set; } - public long Layer3HashOffset { get; set; } - public long Layer3HashSize { get; set; } - public long Field148 { get; set; } - public long Field150 { get; set; } + public long IvfcL1Offset { get; set; } + public long IvfcL1Size { get; set; } + public long IvfcL2Offset { get; set; } + public long IvfcL2Size { get; set; } + public long IvfcL3Offset { get; set; } + public long IvfcL3Size { get; set; } + public long FatOffset { get; set; } + public long FatSize { get; set; } public long DuplexIndex { get; set; } public FsLayout(BinaryReader reader) @@ -171,12 +174,12 @@ namespace LibHac.Savefile JournalDataSizeA = reader.ReadInt64(); JournalDataSizeB = reader.ReadInt64(); SizeReservedArea = reader.ReadInt64(); - L1BitmapOffset0 = reader.ReadInt64(); - L1BitmapOffset1 = reader.ReadInt64(); - L1BitmapSize = reader.ReadInt64(); - MasterHashOffset0 = reader.ReadInt64(); - MasterHashOffset1 = reader.ReadInt64(); - MasterHashSize = reader.ReadInt64(); + DuplexMasterOffsetA = reader.ReadInt64(); + DuplexMasterOffsetB = reader.ReadInt64(); + DuplexMasterSize = reader.ReadInt64(); + IvfcMasterHashOffsetA = reader.ReadInt64(); + IvfcMasterHashOffsetB = reader.ReadInt64(); + IvfcMasterHashSize = reader.ReadInt64(); JournalTableOffset = reader.ReadInt64(); JournalTableSize = reader.ReadInt64(); JournalBitmapUpdatedPhysicalOffset = reader.ReadInt64(); @@ -185,15 +188,15 @@ namespace LibHac.Savefile JournalBitmapUpdatedVirtualSize = reader.ReadInt64(); JournalBitmapUnassignedOffset = reader.ReadInt64(); JournalBitmapUnassignedSize = reader.ReadInt64(); - Layer1HashOffset = reader.ReadInt64(); - Layer1HashSize = reader.ReadInt64(); - Layer2HashOffset = reader.ReadInt64(); - Layer2HashSize = reader.ReadInt64(); - Layer3HashOffset = reader.ReadInt64(); - Layer3HashSize = reader.ReadInt64(); - Field148 = reader.ReadInt64(); - Field150 = reader.ReadInt64(); - DuplexIndex = reader.ReadInt64(); + IvfcL1Offset = reader.ReadInt64(); + IvfcL1Size = reader.ReadInt64(); + IvfcL2Offset = reader.ReadInt64(); + IvfcL2Size = reader.ReadInt64(); + IvfcL3Offset = reader.ReadInt64(); + IvfcL3Size = reader.ReadInt64(); + FatOffset = reader.ReadInt64(); + FatSize = reader.ReadInt64(); + DuplexIndex = reader.ReadByte(); } } @@ -255,24 +258,24 @@ namespace LibHac.Savefile { public string Magic { get; } public uint MagicNum { get; } - public long Field8 { get; } - public long Field10 { get; } + public long TotalSize { get; } + public long JournalSize { get; } public long BlockSize { get; } public int Field20 { get; } - public int MappingEntryCount { get; } - public int Field28 { get; } + public int MainDataBlockCount { get; } + public int JournalBlockCount { get; } public int Field2C { get; } public JournalHeader(BinaryReader reader) { Magic = reader.ReadAscii(4); MagicNum = reader.ReadUInt32(); - Field8 = reader.ReadInt64(); - Field10 = reader.ReadInt64(); + TotalSize = reader.ReadInt64(); + JournalSize = reader.ReadInt64(); BlockSize = reader.ReadInt64(); Field20 = reader.ReadInt32(); - MappingEntryCount = reader.ReadInt32(); - Field28 = reader.ReadInt32(); + MainDataBlockCount = reader.ReadInt32(); + JournalBlockCount = reader.ReadInt32(); Field2C = reader.ReadInt32(); } } @@ -342,4 +345,56 @@ namespace LibHac.Savefile StorageType = reader.ReadInt32(); } } + + public class ExtraData + { + public ulong TitleId { get; } + public Guid UserId { get; } + public ulong SaveId { get; } + public SaveDataType Type { get; } + + public ulong SaveOwnerId { get; } + public long Timestamp { get; } + public long Field50 { get; } + public uint Field54 { get; } + public long DataSize { get; } + public long JournalSize { get; } + + public ExtraData(BinaryReader reader) + { + TitleId = reader.ReadUInt64(); + UserId = ToGuid(reader.ReadBytes(0x10)); + SaveId = reader.ReadUInt64(); + Type = (SaveDataType)reader.ReadByte(); + reader.BaseStream.Position += 0x1f; + + SaveOwnerId = reader.ReadUInt64(); + Timestamp = reader.ReadInt64(); + Field50 = reader.ReadUInt32(); + Field54 = reader.ReadUInt32(); + DataSize = reader.ReadInt64(); + JournalSize = reader.ReadInt64(); + } + + private static Guid ToGuid(byte[] bytes) + { + var b = new byte[0x10]; + Array.Copy(bytes, b, 0x10); + + // The Guid constructor uses a weird, mixed-endian format + Array.Reverse(b, 10, 6); + + return new Guid(b); + } + } + + public enum SaveDataType + { + SystemSaveData, + SaveData, + BcatDeliveryCacheStorage, + DeviceSaveData, + TemporaryStorage, + CacheStorage + } } diff --git a/LibHac/Savefile/Journal.cs b/LibHac/Savefile/Journal.cs index 858eb771..017701cf 100644 --- a/LibHac/Savefile/Journal.cs +++ b/LibHac/Savefile/Journal.cs @@ -109,8 +109,5 @@ namespace LibHac.Savefile { public int PhysicalIndex { get; set; } public int VirtualIndex { get; set; } - public bool UpdatedPhysical { get; set; } - public bool UpdatedVirtual { get; set; } - public bool Unassigned { get; set; } } } diff --git a/LibHac/Savefile/Savefile.cs b/LibHac/Savefile/Savefile.cs index d60d6e85..c483c021 100644 --- a/LibHac/Savefile/Savefile.cs +++ b/LibHac/Savefile/Savefile.cs @@ -93,13 +93,13 @@ namespace LibHac.Savefile JournalBitmapUpdatedPhysical = MetaRemapSource.CreateStream(layout.JournalBitmapUpdatedPhysicalOffset, layout.JournalBitmapUpdatedPhysicalSize); JournalBitmapUpdatedVirtual = MetaRemapSource.CreateStream(layout.JournalBitmapUpdatedVirtualOffset, layout.JournalBitmapUpdatedVirtualSize); JournalBitmapUnassigned = MetaRemapSource.CreateStream(layout.JournalBitmapUnassignedOffset, layout.JournalBitmapUnassignedSize); - JournalLayer1Hash = MetaRemapSource.CreateStream(layout.Layer1HashOffset, layout.Layer1HashSize); - JournalLayer2Hash = MetaRemapSource.CreateStream(layout.Layer2HashOffset, layout.Layer2HashSize); - JournalLayer3Hash = MetaRemapSource.CreateStream(layout.Layer3HashOffset, layout.Layer3HashSize); - JournalFat = MetaRemapSource.CreateStream(layout.Field148, layout.Field150); + JournalLayer1Hash = MetaRemapSource.CreateStream(layout.IvfcL1Offset, layout.IvfcL1Size); + JournalLayer2Hash = MetaRemapSource.CreateStream(layout.IvfcL2Offset, layout.IvfcL2Size); + JournalLayer3Hash = MetaRemapSource.CreateStream(layout.IvfcL3Offset, layout.IvfcL3Size); + JournalFat = MetaRemapSource.CreateStream(layout.FatOffset, layout.FatSize); AllocationTable = new AllocationTable(JournalFat); - var journalMap = JournalStream.ReadMappingEntries(JournalTable, Header.Journal.MappingEntryCount); + var journalMap = JournalStream.ReadMappingEntries(JournalTable, Header.Journal.MainDataBlockCount); var journalData = FileRemapSource.CreateStream(layout.JournalDataOffset, layout.JournalDataSizeB + layout.SizeReservedArea); diff --git a/hactoolnet/ProcessSave.cs b/hactoolnet/ProcessSave.cs new file mode 100644 index 00000000..23abf048 --- /dev/null +++ b/hactoolnet/ProcessSave.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using LibHac; +using LibHac.Savefile; +using static hactoolnet.Print; + +namespace hactoolnet +{ + internal static class ProcessSave + { + public static void Process(Context ctx) + { + using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.ReadWrite)) + { + var save = new Savefile(ctx.Keyset, file, ctx.Options.EnableHash, ctx.Logger); + + if (ctx.Options.OutDir != null) + { + save.Extract(ctx.Options.OutDir, ctx.Logger); + } + + if (ctx.Options.DebugOutDir != null) + { + var dir = ctx.Options.DebugOutDir; + Directory.CreateDirectory(dir); + + File.WriteAllBytes(Path.Combine(dir, "L0_0_MasterHashA"), save.Header.MasterHashA); + File.WriteAllBytes(Path.Combine(dir, "L0_1_MasterHashB"), save.Header.MasterHashB); + File.WriteAllBytes(Path.Combine(dir, "L0_2_DuplexMasterA"), save.Header.DuplexMasterA); + File.WriteAllBytes(Path.Combine(dir, "L0_3_DuplexMasterB"), save.Header.DuplexMasterB); + save.DuplexL1A.WriteAllBytes(Path.Combine(dir, "L0_4_DuplexL1A"), ctx.Logger); + save.DuplexL1B.WriteAllBytes(Path.Combine(dir, "L0_5_DuplexL1B"), ctx.Logger); + save.DuplexDataA.WriteAllBytes(Path.Combine(dir, "L0_6_DuplexDataA"), ctx.Logger); + save.DuplexDataB.WriteAllBytes(Path.Combine(dir, "L0_7_DuplexDataB"), ctx.Logger); + save.JournalData.WriteAllBytes(Path.Combine(dir, "L0_9_JournalData"), ctx.Logger); + + save.DuplexData.WriteAllBytes(Path.Combine(dir, "L1_0_DuplexData"), ctx.Logger); + save.JournalTable.WriteAllBytes(Path.Combine(dir, "L2_0_JournalTable"), ctx.Logger); + save.JournalBitmapUpdatedPhysical.WriteAllBytes(Path.Combine(dir, "L2_1_JournalBitmapUpdatedPhysical"), ctx.Logger); + save.JournalBitmapUpdatedVirtual.WriteAllBytes(Path.Combine(dir, "L2_2_JournalBitmapUpdatedVirtual"), ctx.Logger); + save.JournalBitmapUnassigned.WriteAllBytes(Path.Combine(dir, "L2_3_JournalBitmapUnassigned"), ctx.Logger); + save.JournalLayer1Hash.WriteAllBytes(Path.Combine(dir, "L2_4_Layer1Hash"), ctx.Logger); + save.JournalLayer2Hash.WriteAllBytes(Path.Combine(dir, "L2_5_Layer2Hash"), ctx.Logger); + save.JournalLayer3Hash.WriteAllBytes(Path.Combine(dir, "L2_6_Layer3Hash"), ctx.Logger); + save.JournalFat.WriteAllBytes(Path.Combine(dir, "L2_7_FAT"), ctx.Logger); + + save.IvfcStreamSource.CreateStream().WriteAllBytes(Path.Combine(dir, "L3_0_SaveData"), ctx.Logger); + } + + if (ctx.Options.SignSave) + { + if (save.SignHeader(ctx.Keyset)) + { + ctx.Logger.LogMessage("Successfully signed save file"); + } + else + { + ctx.Logger.LogMessage("Unable to sign save file. Do you have all the required keys?"); + } + } + + ctx.Logger.LogMessage(save.Print()); + } + } + + private static string Print(this Savefile save) + { + int colLen = 25; + var sb = new StringBuilder(); + sb.AppendLine(); + + sb.AppendLine("Savefile:"); + PrintItem(sb, colLen, "CMAC Signature:", save.Header.Cmac); + PrintItem(sb, colLen, "Title ID:", $"{save.Header.ExtraData.TitleId:x16}"); + PrintItem(sb, colLen, "User ID:", save.Header.ExtraData.UserId); + PrintItem(sb, colLen, "Save ID:", $"{save.Header.ExtraData.SaveId:x16}"); + PrintItem(sb, colLen, "Save Type:", $"{save.Header.ExtraData.Type}"); + PrintItem(sb, colLen, "Owner ID:", $"{save.Header.ExtraData.SaveOwnerId:x16}"); + PrintItem(sb, colLen, "Timestamp:", $"{DateTimeOffset.FromUnixTimeSeconds(save.Header.ExtraData.Timestamp):yyyy-MM-dd HH:mm:ss} UTC"); + 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); + } + } + + return sb.ToString(); + } + } +} diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index d0de4993..8b180180 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -2,7 +2,6 @@ using System.IO; using System.Text; using LibHac; -using LibHac.Savefile; namespace hactoolnet { @@ -42,7 +41,7 @@ namespace hactoolnet ProcessSwitchFs.Process(ctx); break; case FileType.Save: - ProcessSave(ctx); + ProcessSave.Process(ctx); break; case FileType.Xci: ProcessXci.Process(ctx); @@ -100,59 +99,6 @@ namespace hactoolnet } } - private static void ProcessSave(Context ctx) - { - using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.ReadWrite)) - { - var save = new Savefile(ctx.Keyset, file, ctx.Options.EnableHash, ctx.Logger); - - if (ctx.Options.OutDir != null) - { - save.Extract(ctx.Options.OutDir, ctx.Logger); - } - - if (ctx.Options.DebugOutDir != null) - { - var dir = ctx.Options.DebugOutDir; - Directory.CreateDirectory(dir); - - File.WriteAllBytes(Path.Combine(dir, "L0_0_MasterHashA"), save.Header.MasterHashA); - File.WriteAllBytes(Path.Combine(dir, "L0_1_MasterHashB"), save.Header.MasterHashB); - File.WriteAllBytes(Path.Combine(dir, "L0_2_DuplexMasterA"), save.Header.DuplexMasterA); - File.WriteAllBytes(Path.Combine(dir, "L0_3_DuplexMasterB"), save.Header.DuplexMasterB); - save.DuplexL1A.WriteAllBytes(Path.Combine(dir, "L0_4_DuplexL1A"), ctx.Logger); - save.DuplexL1B.WriteAllBytes(Path.Combine(dir, "L0_5_DuplexL1B"), ctx.Logger); - save.DuplexDataA.WriteAllBytes(Path.Combine(dir, "L0_6_DuplexDataA"), ctx.Logger); - save.DuplexDataB.WriteAllBytes(Path.Combine(dir, "L0_7_DuplexDataB"), ctx.Logger); - save.JournalData.WriteAllBytes(Path.Combine(dir, "L0_9_JournalData"), ctx.Logger); - - save.DuplexData.WriteAllBytes(Path.Combine(dir, "L1_0_DuplexData"), ctx.Logger); - save.JournalTable.WriteAllBytes(Path.Combine(dir, "L2_0_JournalTable"), ctx.Logger); - save.JournalBitmapUpdatedPhysical.WriteAllBytes(Path.Combine(dir, "L2_1_JournalBitmapUpdatedPhysical"), ctx.Logger); - save.JournalBitmapUpdatedVirtual.WriteAllBytes(Path.Combine(dir, "L2_2_JournalBitmapUpdatedVirtual"), ctx.Logger); - save.JournalBitmapUnassigned.WriteAllBytes(Path.Combine(dir, "L2_3_JournalBitmapUnassigned"), ctx.Logger); - save.JournalLayer1Hash.WriteAllBytes(Path.Combine(dir, "L2_4_Layer1Hash"), ctx.Logger); - save.JournalLayer2Hash.WriteAllBytes(Path.Combine(dir, "L2_5_Layer2Hash"), ctx.Logger); - save.JournalLayer3Hash.WriteAllBytes(Path.Combine(dir, "L2_6_Layer3Hash"), ctx.Logger); - save.JournalFat.WriteAllBytes(Path.Combine(dir, "L2_7_FAT"), ctx.Logger); - - save.IvfcStreamSource.CreateStream().WriteAllBytes(Path.Combine(dir, "L3_0_SaveData"), ctx.Logger); - } - - if (ctx.Options.SignSave) - { - if (save.SignHeader(ctx.Keyset)) - { - ctx.Logger.LogMessage("Successfully signed save file"); - } - else - { - ctx.Logger.LogMessage("Unable to sign save file. Do you have all the required keys?"); - } - } - } - } - private static void ProcessKeygen(Context ctx) { Console.WriteLine(ExternalKeys.PrintCommonKeys(ctx.Keyset)); @@ -161,8 +107,8 @@ namespace hactoolnet // For running random stuff // ReSharper disable once UnusedParameter.Local private static void CustomTask(Context ctx) - { - + { + } } }