diff --git a/KEYS.md b/KEYS.md index 1a16a865..e8aa1be2 100644 --- a/KEYS.md +++ b/KEYS.md @@ -95,6 +95,8 @@ bis_key_source_01 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX bis_key_source_02 = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX bis_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +save_mac_kek_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +save_mac_key_source = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ``` ### Console-unique keys @@ -141,6 +143,8 @@ bis_kek_source bis_key_source_00 bis_key_source_01 bis_key_source_02 +save_mac_kek_source +save_mac_key_source header_key xci_header_key diff --git a/LibHac/Savefile/Savefile.cs b/LibHac/Savefile/Savefile.cs index 2d61b7db..b7389d8d 100644 --- a/LibHac/Savefile/Savefile.cs +++ b/LibHac/Savefile/Savefile.cs @@ -9,6 +9,7 @@ namespace LibHac.Savefile { public Header Header { get; } private RemapStream FileRemap { get; } + public SharedStreamSource SavefileSource { get; } public SharedStreamSource FileRemapSource { get; } private RemapStream MetaRemap { get; } public SharedStreamSource MetaRemapSource { get; } @@ -39,12 +40,15 @@ namespace LibHac.Savefile public Savefile(Keyset keyset, Stream file, IProgressReport logger = null) { - using (var reader = new BinaryReader(file, Encoding.Default, true)) + SavefileSource = new SharedStreamSource(file); + + using (var reader = new BinaryReader(SavefileSource.CreateStream(), Encoding.Default, true)) { Header = new Header(keyset, reader, logger); - var layout = Header.Layout; + FsLayout layout = Header.Layout; + FileRemap = new RemapStream( - new SubStream(file, layout.FileMapDataOffset, layout.FileMapDataSize), + SavefileSource.CreateStream(layout.FileMapDataOffset, layout.FileMapDataSize), Header.FileMapEntries, Header.FileRemap.MapSegmentCount); FileRemapSource = new SharedStreamSource(FileRemap); @@ -220,6 +224,25 @@ namespace LibHac.Savefile return entries; } + + public bool SignHeader(Keyset keyset) + { + if (keyset.SaveMacKey.IsEmpty()) return false; + + var data = new byte[0x200]; + var cmac = new byte[0x10]; + + var headerStream = SavefileSource.CreateStream(); + headerStream.Position = 0x100; + headerStream.Read(data, 0, 0x200); + + Crypto.CalculateAesCmac(keyset.SaveMacKey, data, 0, cmac, 0, 0x200); + + headerStream.Position = 0; + headerStream.Write(cmac, 0, 0x10); + + return true; + } } public static class SavefileExtensions diff --git a/README.md b/README.md index 5b53ba5f..a267035f 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Switch FS options: Savefile options: --outdir Specify directory path to save contents to. --debugoutdir Specify directory path to save intermediate data to for debugging. + --sign Sign the save file. (Requires device_key in key file) ``` ## Examples diff --git a/hactoolnet/CliParser.cs b/hactoolnet/CliParser.cs index 466860e8..b92b37ba 100644 --- a/hactoolnet/CliParser.cs +++ b/hactoolnet/CliParser.cs @@ -43,6 +43,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("sign", 0, (o, a) => o.SignSave = true), new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])), }; @@ -194,6 +195,7 @@ namespace hactoolnet sb.AppendLine("Savefile options:"); 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)"); return sb.ToString(); } diff --git a/hactoolnet/Options.cs b/hactoolnet/Options.cs index f4ec7bf0..2b051435 100644 --- a/hactoolnet/Options.cs +++ b/hactoolnet/Options.cs @@ -33,6 +33,7 @@ namespace hactoolnet public bool ListApps; public bool ListTitles; public bool ListRomFs; + public bool SignSave; public ulong TitleId; } diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index 3cc0e948..6fe5f10b 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -93,7 +93,7 @@ namespace hactoolnet private static void ProcessSave(Context ctx) { - using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) + using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.ReadWrite)) { var save = new Savefile(ctx.Keyset, file, ctx.Logger); @@ -129,6 +129,18 @@ namespace hactoolnet save.JournalStreamSource.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?"); + } + } } }