hactoolnet: Add option to replace a savedata's entire contents

This commit is contained in:
Alex Barney 2019-05-08 18:06:29 -05:00
parent 26ac0ed975
commit c766a413e2
4 changed files with 44 additions and 29 deletions

View file

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp2.1;net46</TargetFrameworks> <TargetFrameworks>netcoreapp2.1;net46</TargetFrameworks>
<LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -46,6 +46,7 @@ namespace hactoolnet
new CliOption("normaldir", 1, (o, a) => o.NormalDir = a[0]), new CliOption("normaldir", 1, (o, a) => o.NormalDir = a[0]),
new CliOption("securedir", 1, (o, a) => o.SecureDir = a[0]), new CliOption("securedir", 1, (o, a) => o.SecureDir = a[0]),
new CliOption("logodir", 1, (o, a) => o.LogoDir = a[0]), new CliOption("logodir", 1, (o, a) => o.LogoDir = a[0]),
new CliOption("repack", 1, (o, a) => o.RepackSource = a[0]),
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("listncas", 0, (o, a) => o.ListNcas = true), new CliOption("listncas", 0, (o, a) => o.ListNcas = true),
@ -235,6 +236,7 @@ namespace hactoolnet
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(" --trim Trim garbage data in the save file. (Requires device_key in key file)"); sb.AppendLine(" --trim Trim garbage data in the save file. (Requires device_key in key file)");
sb.AppendLine(" --listfiles List files in save file."); sb.AppendLine(" --listfiles List files in save file.");
sb.AppendLine(" --repack <dir> Replaces the contents of the save data with the specified directory.");
sb.AppendLine(" --replacefile <filename in save> <file> Replaces a file in the save data"); sb.AppendLine(" --replacefile <filename in save> <file> Replaces a file in the save data");
sb.AppendLine("NDV0 (Delta) options:"); sb.AppendLine("NDV0 (Delta) options:");
sb.AppendLine(" Input delta patch can be a delta NCA file or a delta fragment file."); sb.AppendLine(" Input delta patch can be a delta NCA file or a delta fragment file.");

View file

@ -39,6 +39,7 @@ namespace hactoolnet
public string LogoDir; public string LogoDir;
public string ReplaceFileSource; public string ReplaceFileSource;
public string ReplaceFileDest; public string ReplaceFileDest;
public string RepackSource;
public bool ListApps; public bool ListApps;
public bool ListTitles; public bool ListTitles;
public bool ListNcas; public bool ListNcas;

View file

@ -16,6 +16,8 @@ namespace hactoolnet
{ {
using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.ReadWrite)) using (var file = new LocalStorage(ctx.Options.InFile, FileAccess.ReadWrite))
{ {
bool signNeeded = ctx.Options.SignSave;
var save = new SaveDataFileSystem(ctx.Keyset, file, ctx.Options.IntegrityLevel, true); var save = new SaveDataFileSystem(ctx.Keyset, file, ctx.Options.IntegrityLevel, true);
if (ctx.Options.Validate) if (ctx.Options.Validate)
@ -30,52 +32,61 @@ namespace hactoolnet
if (ctx.Options.DebugOutDir != null) if (ctx.Options.DebugOutDir != null)
{ {
// todo
string dir = ctx.Options.DebugOutDir; string dir = ctx.Options.DebugOutDir;
ExportSaveDebug(ctx, dir, save); ExportSaveDebug(ctx, dir, save);
} }
if (ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null) try
{ {
string destFilename = ctx.Options.ReplaceFileDest; if (ctx.Options.ReplaceFileDest != null && ctx.Options.ReplaceFileSource != null)
if (!destFilename.StartsWith("/")) destFilename = '/' + destFilename;
using (IFile inFile = new LocalFile(ctx.Options.ReplaceFileSource, OpenMode.Read))
{ {
using (IFile outFile = save.OpenFile(destFilename, OpenMode.ReadWrite)) string destFilename = ctx.Options.ReplaceFileDest;
if (!destFilename.StartsWith("/")) destFilename = '/' + destFilename;
using (IFile inFile = new LocalFile(ctx.Options.ReplaceFileSource, OpenMode.Read))
{ {
if (inFile.GetSize() != outFile.GetSize()) using (IFile outFile = save.OpenFile(destFilename, OpenMode.ReadWrite))
{ {
outFile.SetSize(inFile.GetSize()); if (inFile.GetSize() != outFile.GetSize())
{
outFile.SetSize(inFile.GetSize());
}
inFile.CopyTo(outFile, ctx.Logger);
ctx.Logger.LogMessage($"Replaced file {destFilename}");
} }
inFile.CopyTo(outFile, ctx.Logger);
ctx.Logger.LogMessage($"Replaced file {destFilename}");
} }
signNeeded = true;
} }
if (save.Commit(ctx.Keyset)) if (ctx.Options.RepackSource != null)
{ {
ctx.Logger.LogMessage("Successfully signed save file"); var source = new LocalFileSystem(ctx.Options.RepackSource);
}
else
{
ctx.Logger.LogMessage("ERROR: Unable to sign save file. Do you have all the required keys?");
}
return; save.CleanDirectoryRecursively("/");
save.Commit(ctx.Keyset);
source.CopyFileSystem(save);
signNeeded = true;
}
}
finally
{
save.Commit(ctx.Keyset);
} }
if (ctx.Options.SignSave || ctx.Options.TrimSave) if (ctx.Options.TrimSave)
{ {
if (ctx.Options.TrimSave) save.FsTrim();
{ signNeeded = true;
save.FsTrim(); ctx.Logger.LogMessage("Trimmed save file");
ctx.Logger.LogMessage("Trimmed save file"); }
}
if (signNeeded)
{
if (save.Commit(ctx.Keyset)) if (save.Commit(ctx.Keyset))
{ {
ctx.Logger.LogMessage("Successfully signed save file"); ctx.Logger.LogMessage("Successfully signed save file");
@ -90,14 +101,14 @@ namespace hactoolnet
if (ctx.Options.ListFiles) if (ctx.Options.ListFiles)
{ {
foreach (DirectoryEntry entry in save.EnumerateEntries().Where(x => x.Type == DirectoryEntryType.File)) foreach (DirectoryEntry entry in save.EnumerateEntries())
{ {
ctx.Logger.LogMessage(entry.FullPath); ctx.Logger.LogMessage(entry.FullPath);
} }
} }
ctx.Logger.LogMessage(save.Print()); ctx.Logger.LogMessage(save.Print());
//ctx.Logger.LogMessage(PrintFatLayout(save)); //ctx.Logger.LogMessage(PrintFatLayout(save.SaveDataFileSystemCore));
} }
} }