From 1b3a0363ceab548479519815d2b7c096358c7732 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Tue, 11 Dec 2018 15:11:44 -0600 Subject: [PATCH] hactoolnet: Add an option to replace a file in save data --- src/hactoolnet/CliParser.cs | 9 ++++++++- src/hactoolnet/Options.cs | 2 ++ src/hactoolnet/ProcessSave.cs | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/hactoolnet/CliParser.cs b/src/hactoolnet/CliParser.cs index 13838592..1ebb1ffb 100644 --- a/src/hactoolnet/CliParser.cs +++ b/src/hactoolnet/CliParser.cs @@ -50,6 +50,12 @@ namespace hactoolnet new CliOption("sign", 0, (o, a) => o.SignSave = true), new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])), new CliOption("bench", 1, (o, a) => o.BenchType = a[0]), + + new CliOption("replacefile", 2, (o, a) => + { + o.ReplaceFileDest = a[0]; + o.ReplaceFileSource = a[1]; + }) }; public static Options Parse(string[] args) @@ -204,11 +210,12 @@ namespace hactoolnet sb.AppendLine(" --romfsdir Specify RomFS directory path. (--title must be specified)"); sb.AppendLine(" --savedir Specify save file directory path."); sb.AppendLine(" -y, --verify Verify all titles, or verify a single title if --title is set."); - sb.AppendLine("Savefile options:"); + sb.AppendLine("Save data 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)"); sb.AppendLine(" --listfiles List files in save file."); + sb.AppendLine(" --replacefile Replaces a file in the save data"); sb.AppendLine("Keygen options:"); sb.AppendLine(" --outdir Specify directory path to save key files to."); diff --git a/src/hactoolnet/Options.cs b/src/hactoolnet/Options.cs index 6efce3d7..b7e3d697 100644 --- a/src/hactoolnet/Options.cs +++ b/src/hactoolnet/Options.cs @@ -33,6 +33,8 @@ namespace hactoolnet public string NormalDir; public string SecureDir; public string LogoDir; + public string ReplaceFileSource; + public string ReplaceFileDest; public bool ListApps; public bool ListTitles; public bool ListNcas; diff --git a/src/hactoolnet/ProcessSave.cs b/src/hactoolnet/ProcessSave.cs index af740d2c..1fab9fe9 100644 --- a/src/hactoolnet/ProcessSave.cs +++ b/src/hactoolnet/ProcessSave.cs @@ -94,6 +94,39 @@ namespace hactoolnet duplexDataB.WriteAllBytes(Path.Combine(duplexDir, "DataB"), ctx.Logger); } + if (ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null) + { + string destFilename = ctx.Options.ReplaceFileDest; + if (!destFilename.StartsWith("/")) destFilename = '/' + destFilename; + + using (IStorage inFile = new FileStream(ctx.Options.ReplaceFileSource, FileMode.Open, FileAccess.Read).AsStorage(false)) + { + using (IStorage outFile = save.OpenFile(destFilename)) + { + if (inFile.Length != outFile.Length) + { + ctx.Logger.LogMessage($"Replacement file must be the same size as the original file. ({outFile.Length} bytes)"); + return; + } + + inFile.CopyTo(outFile, ctx.Logger); + + ctx.Logger.LogMessage($"Replaced file {destFilename}"); + } + } + + if (save.CommitHeader(ctx.Keyset)) + { + ctx.Logger.LogMessage("Successfully signed save file"); + } + else + { + ctx.Logger.LogMessage("ERROR: Unable to sign save file. Do you have all the required keys?"); + } + + return; + } + if (ctx.Options.SignSave) { if (save.CommitHeader(ctx.Keyset)) @@ -104,6 +137,8 @@ namespace hactoolnet { ctx.Logger.LogMessage("Unable to sign save file. Do you have all the required keys?"); } + + return; } if (ctx.Options.ListFiles)