mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Read save files from SD card
This commit is contained in:
parent
01f5ed7ccf
commit
49a1f0b5a4
6 changed files with 89 additions and 24 deletions
|
@ -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);
|
||||
|
||||
// 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))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateKeys(Keyset keyset, Stream stream)
|
||||
{
|
||||
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);
|
||||
|
||||
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()
|
||||
|
|
|
@ -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,10 +28,19 @@ 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
|
||||
{
|
||||
ContentsDir = fs.GetFullPath("Contents");
|
||||
if (fs.DirectoryExists("Contents"))
|
||||
{
|
||||
ContentsDir = fs.GetFullPath("Contents");
|
||||
}
|
||||
|
||||
if (fs.DirectoryExists("save"))
|
||||
{
|
||||
SaveDir = fs.GetFullPath("save");
|
||||
}
|
||||
}
|
||||
|
||||
if (ContentsDir == null)
|
||||
|
@ -37,6 +48,7 @@ namespace LibHac
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,7 +177,7 @@ namespace LibHac
|
|||
{
|
||||
var romfs = new Romfs(title.ControlNca.OpenSection(0, false));
|
||||
var control = romfs.GetFile("/control.nacp");
|
||||
|
||||
|
||||
var reader = new BinaryReader(new MemoryStream(control));
|
||||
title.Control = new Nacp(reader);
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue