2018-10-01 19:55:10 +02:00
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using LibHac;
|
2018-10-13 22:12:10 +02:00
|
|
|
|
using LibHac.Save;
|
2018-10-01 19:55:10 +02:00
|
|
|
|
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))
|
|
|
|
|
{
|
2018-10-09 22:33:56 +02:00
|
|
|
|
var save = new Savefile(ctx.Keyset, file, ctx.Options.IntegrityLevel);
|
2018-10-01 19:55:10 +02:00
|
|
|
|
|
|
|
|
|
if (ctx.Options.OutDir != null)
|
|
|
|
|
{
|
|
|
|
|
save.Extract(ctx.Options.OutDir, ctx.Logger);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.DebugOutDir != null)
|
|
|
|
|
{
|
2018-10-03 00:25:58 +02:00
|
|
|
|
string dir = ctx.Options.DebugOutDir;
|
2018-10-01 19:55:10 +02:00
|
|
|
|
Directory.CreateDirectory(dir);
|
|
|
|
|
|
2018-10-16 03:53:49 +02:00
|
|
|
|
FsLayout layout = save.Header.Layout;
|
|
|
|
|
|
2018-10-01 19:55:10 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-10-16 03:53:49 +02:00
|
|
|
|
Stream duplexL1A = save.DataRemapStorage.OpenStream(layout.DuplexL1OffsetA, layout.DuplexL1Size);
|
|
|
|
|
Stream duplexL1B = save.DataRemapStorage.OpenStream(layout.DuplexL1OffsetB, layout.DuplexL1Size);
|
|
|
|
|
Stream duplexDataA = save.DataRemapStorage.OpenStream(layout.DuplexDataOffsetA, layout.DuplexDataSize);
|
|
|
|
|
Stream duplexDataB = save.DataRemapStorage.OpenStream(layout.DuplexDataOffsetB, layout.DuplexDataSize);
|
|
|
|
|
Stream journalData = save.DataRemapStorage.OpenStream(layout.JournalDataOffset, layout.JournalDataSizeB + layout.SizeReservedArea);
|
|
|
|
|
|
|
|
|
|
duplexL1A.WriteAllBytes(Path.Combine(dir, "L0_4_DuplexL1A"), ctx.Logger);
|
|
|
|
|
duplexL1B.WriteAllBytes(Path.Combine(dir, "L0_5_DuplexL1B"), ctx.Logger);
|
|
|
|
|
duplexDataA.WriteAllBytes(Path.Combine(dir, "L0_6_DuplexDataA"), ctx.Logger);
|
|
|
|
|
duplexDataB.WriteAllBytes(Path.Combine(dir, "L0_7_DuplexDataB"), ctx.Logger);
|
|
|
|
|
journalData.WriteAllBytes(Path.Combine(dir, "L0_9_JournalData"), ctx.Logger);
|
2018-10-01 19:55:10 +02:00
|
|
|
|
save.DuplexData.WriteAllBytes(Path.Combine(dir, "L1_0_DuplexData"), ctx.Logger);
|
2018-10-16 03:53:49 +02:00
|
|
|
|
|
|
|
|
|
Stream journalTable = save.MetaRemapStorage.OpenStream(layout.JournalTableOffset, layout.JournalTableSize);
|
|
|
|
|
Stream journalBitmapUpdatedPhysical = save.MetaRemapStorage.OpenStream(layout.JournalBitmapUpdatedPhysicalOffset, layout.JournalBitmapUpdatedPhysicalSize);
|
|
|
|
|
Stream journalBitmapUpdatedVirtual = save.MetaRemapStorage.OpenStream(layout.JournalBitmapUpdatedVirtualOffset, layout.JournalBitmapUpdatedVirtualSize);
|
|
|
|
|
Stream journalBitmapUnassigned = save.MetaRemapStorage.OpenStream(layout.JournalBitmapUnassignedOffset, layout.JournalBitmapUnassignedSize);
|
|
|
|
|
Stream journalLayer1Hash = save.MetaRemapStorage.OpenStream(layout.IvfcL1Offset, layout.IvfcL1Size);
|
|
|
|
|
Stream journalLayer2Hash = save.MetaRemapStorage.OpenStream(layout.IvfcL2Offset, layout.IvfcL2Size);
|
|
|
|
|
Stream journalLayer3Hash = save.MetaRemapStorage.OpenStream(layout.IvfcL3Offset, layout.IvfcL3Size);
|
|
|
|
|
Stream journalFat = save.MetaRemapStorage.OpenStream(layout.FatOffset, layout.FatSize);
|
|
|
|
|
|
|
|
|
|
journalTable.WriteAllBytes(Path.Combine(dir, "L2_0_JournalTable"), ctx.Logger);
|
|
|
|
|
journalBitmapUpdatedPhysical.WriteAllBytes(Path.Combine(dir, "L2_1_JournalBitmapUpdatedPhysical"), ctx.Logger);
|
|
|
|
|
journalBitmapUpdatedVirtual.WriteAllBytes(Path.Combine(dir, "L2_2_JournalBitmapUpdatedVirtual"), ctx.Logger);
|
|
|
|
|
journalBitmapUnassigned.WriteAllBytes(Path.Combine(dir, "L2_3_JournalBitmapUnassigned"), ctx.Logger);
|
|
|
|
|
journalLayer1Hash.WriteAllBytes(Path.Combine(dir, "L2_4_Layer1Hash"), ctx.Logger);
|
|
|
|
|
journalLayer2Hash.WriteAllBytes(Path.Combine(dir, "L2_5_Layer2Hash"), ctx.Logger);
|
|
|
|
|
journalLayer3Hash.WriteAllBytes(Path.Combine(dir, "L2_6_Layer3Hash"), ctx.Logger);
|
|
|
|
|
journalFat.WriteAllBytes(Path.Combine(dir, "L2_7_FAT"), ctx.Logger);
|
2018-10-01 19:55:10 +02:00
|
|
|
|
|
|
|
|
|
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:");
|
2018-10-18 00:15:57 +02:00
|
|
|
|
PrintItem(sb, colLen, $"CMAC Signature{save.Header.SignatureValidity.GetValidityString()}:", save.Header.Cmac);
|
2018-10-01 19:55:10 +02:00
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|