Read save files from SD card

This commit is contained in:
Alex Barney 2018-09-10 22:09:35 -05:00
parent 01f5ed7ccf
commit 49a1f0b5a4
6 changed files with 89 additions and 24 deletions

View file

@ -21,11 +21,10 @@ namespace LibHac
stream.Position = 0;
KeepOpen = keepOpen;
ReadHeader(stream);
DeriveKeys(keyset, sdPath);
ValidateKeys(keyset, stream);
DeriveKeys(keyset, sdPath, stream);
stream.Position = 0x4000;
var xts = XtsAes128.Create(Keys[0], Keys[1]);
Xts xts = XtsAes128.Create(Keys[0], Keys[1]);
Stream = new RandomAccessSectorStream(new XtsSectorStream(stream, xts, 0x4000, 0x4000), keepOpen);
}
@ -44,38 +43,45 @@ namespace LibHac
Length = reader.ReadInt64();
}
private void DeriveKeys(Keyset keyset, string sdPath)
private void DeriveKeys(Keyset keyset, string sdPath, Stream stream)
{
stream.Position = 0x20;
var validationHashKey = new byte[0x60];
stream.Read(validationHashKey, 0, 0x60);
// Try both the NCA and save key sources and pick the one that works
for (int k = 0; k < 2; k++)
{
var naxSpecificKeys = Util.CreateJaggedArray<byte[][]>(2, 0x10);
var hashKey = new byte[0x10];
Array.Copy(keyset.sd_card_keys[k], hashKey, 0x10);
// Use the sd path to generate the kek for this NAX0
var hash = new HMACSHA256(hashKey);
var sdPathBytes = Encoding.ASCII.GetBytes(sdPath);
var checksum = hash.ComputeHash(sdPathBytes, 0, sdPathBytes.Length);
byte[] sdPathBytes = Encoding.ASCII.GetBytes(sdPath);
byte[] checksum = hash.ComputeHash(sdPathBytes, 0, sdPathBytes.Length);
Array.Copy(checksum, 0, naxSpecificKeys[0], 0, 0x10);
Array.Copy(checksum, 0x10, naxSpecificKeys[1], 0, 0x10);
// Decrypt this NAX0's keys
Crypto.DecryptEcb(naxSpecificKeys[0], EncKeys[0], Keys[0], 0x10);
Crypto.DecryptEcb(naxSpecificKeys[1], EncKeys[1], Keys[1], 0x10);
}
}
private void ValidateKeys(Keyset keyset, Stream stream)
// Copy the decrypted keys into the NAX0 header and use that for the HMAC key
// for validating that the keys are correct
Array.Copy(Keys[0], 0, validationHashKey, 8, 0x10);
Array.Copy(Keys[1], 0, validationHashKey, 0x18, 0x10);
var validationHash = new HMACSHA256(validationHashKey);
byte[] validationMac = validationHash.ComputeHash(keyset.sd_card_keys[k], 0x10, 0x10);
if (Util.ArraysEqual(Hmac, validationMac))
{
stream.Position = 0x20;
var hashKey = new byte[0x60];
stream.Read(hashKey, 0, 0x60);
Array.Copy(Keys[0], 0, hashKey, 8, 0x10);
Array.Copy(Keys[1], 0, hashKey, 0x18, 0x10);
return;
}
}
var hash = new HMACSHA256(hashKey);
var validationMac = hash.ComputeHash(keyset.sd_card_keys[1], 0x10, 0x10);
var isValid = Util.ArraysEqual(Hmac, validationMac);
if (!isValid) throw new ArgumentException("NAX0 key derivation failed.");
throw new ArgumentException("NAX0 key derivation failed.");
}
public void Dispose()

View file

@ -13,8 +13,10 @@ namespace LibHac
public Keyset Keyset { get; }
public IFileSystem Fs { get; }
public string ContentsDir { get; }
public string SaveDir { get; }
public Dictionary<string, Nca> Ncas { get; } = new Dictionary<string, Nca>(StringComparer.OrdinalIgnoreCase);
public Dictionary<string, Savefile.Savefile> Saves { get; } = new Dictionary<string, Savefile.Savefile>(StringComparer.OrdinalIgnoreCase);
public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>();
public Dictionary<ulong, Application> Applications { get; } = new Dictionary<ulong, Application>();
@ -26,17 +28,27 @@ namespace LibHac
if (fs.DirectoryExists("Nintendo"))
{
ContentsDir = fs.GetFullPath(Path.Combine("Nintendo", "Contents"));
SaveDir = fs.GetFullPath(Path.Combine("Nintendo", "save"));
}
else if (fs.DirectoryExists("Contents"))
else
{
if (fs.DirectoryExists("Contents"))
{
ContentsDir = fs.GetFullPath("Contents");
}
if (fs.DirectoryExists("save"))
{
SaveDir = fs.GetFullPath("save");
}
}
if (ContentsDir == null)
{
throw new DirectoryNotFoundException("Could not find \"Contents\" directory");
}
OpenAllSaves();
OpenAllNcas();
ReadTitles();
ReadControls();
@ -87,6 +99,35 @@ namespace LibHac
}
}
private void OpenAllSaves()
{
string[] files = Fs.GetFileSystemEntries(SaveDir, "*");
foreach (string file in files)
{
Savefile.Savefile save = null;
string saveName = Path.GetFileNameWithoutExtension(file);
try
{
Stream stream = Fs.OpenFile(file, FileMode.Open);
string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/');
var nax0 = new Nax0(Keyset, stream, sdPath, false);
save = new Savefile.Savefile(nax0.Stream);
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message} {file}");
}
if (save != null && saveName != null)
{
Saves[saveName] = save;
}
}
}
private void ReadTitles()
{
foreach (var nca in Ncas.Values.Where(x => x.Header.ContentType == ContentType.Meta))
@ -126,7 +167,7 @@ namespace LibHac
}
}
Titles.Add(title.Id, title);
Titles[title.Id] = title;
}
}

View file

@ -56,6 +56,7 @@ Switch FS options:
--exefsdir <dir> Specify ExeFS directory path. (--title must be specified)
--romfs <file> Specify RomFS directory path. (--title must be specified)
--romfsdir <dir> Specify RomFS directory path. (--title must be specified)
--savedir <dir> Specify save file directory path.
Savefile options:
--outdir <dir> Specify directory path to save contents to.
--debugoutdir <dir> Specify directory path to save intermediate data to for debugging.

View file

@ -29,6 +29,7 @@ namespace hactoolnet
new CliOption("romfs", 1, (o, a) => o.RomfsOut = a[0]),
new CliOption("romfsdir", 1, (o, a) => o.RomfsOutDir = a[0]),
new CliOption("debugoutdir", 1, (o, a) => o.DebugOutDir = a[0]),
new CliOption("savedir", 1, (o, a) => o.SaveOutDir = a[0]),
new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]),
new CliOption("nspout", 1, (o, a) => o.NspOut = a[0]),
new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]),
@ -184,6 +185,7 @@ namespace hactoolnet
sb.AppendLine(" --exefsdir <dir> Specify ExeFS directory path. (--title must be specified)");
sb.AppendLine(" --romfs <file> Specify RomFS directory path. (--title must be specified)");
sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path. (--title must be specified)");
sb.AppendLine(" --savedir <dir> Specify save file directory path.");
sb.AppendLine("Savefile options:");
sb.AppendLine(" --outdir <dir> Specify directory path to save contents to.");
sb.AppendLine(" --debugoutdir <dir> Specify directory path to save intermediate data to for debugging.");

View file

@ -19,6 +19,7 @@ namespace hactoolnet
public string RomfsOut;
public string RomfsOutDir;
public string DebugOutDir;
public string SaveOutDir;
public string OutDir;
public string SdSeed;
public string NspOut;

View file

@ -256,6 +256,11 @@ namespace hactoolnet
{
CreateNsp(ctx, switchFs);
}
if (ctx.Options.SaveOutDir != null)
{
ExportSdSaves(ctx, switchFs);
}
}
private static void ProcessXci(Context ctx)
@ -650,6 +655,15 @@ namespace hactoolnet
return sb.ToString();
}
private static void ExportSdSaves(Context ctx, SwitchFs switchFs)
{
foreach (var save in switchFs.Saves)
{
var outDir = Path.Combine(ctx.Options.SaveOutDir, save.Key);
save.Value.Extract(outDir, ctx.Logger);
}
}
}
}