diff --git a/src/LibHac/FsSystem/AesXtsFile.cs b/src/LibHac/FsSystem/AesXtsFile.cs
index e4308efb..b143e8cb 100644
--- a/src/LibHac/FsSystem/AesXtsFile.cs
+++ b/src/LibHac/FsSystem/AesXtsFile.cs
@@ -12,7 +12,7 @@ namespace LibHac.FsSystem
private int BlockSize { get; }
private OpenMode Mode { get; }
- private AesXtsFileHeader Header { get; }
+ public AesXtsFileHeader Header { get; }
private IStorage BaseStorage { get; }
internal const int HeaderLength = 0x4000;
diff --git a/src/hactoolnet/CliParser.cs b/src/hactoolnet/CliParser.cs
index 0dc6b6f9..386aa032 100644
--- a/src/hactoolnet/CliParser.cs
+++ b/src/hactoolnet/CliParser.cs
@@ -272,6 +272,10 @@ namespace hactoolnet
sb.AppendLine(" --listfiles List files in save file.");
sb.AppendLine(" --repack
Replaces the contents of the save data with the specified directory.");
sb.AppendLine(" --replacefile Replaces a file in the save data");
+ sb.AppendLine("NAX0 options:");
+ sb.AppendLine(" --sdseed Set console unique seed for SD card NAX0 encryption.");
+ sb.AppendLine(" --sdpath Set relative path for NAX0 key derivation (ex: /registered/000000FF/cafebabecafebabecafebabecafebabe.nca).");
+ sb.AppendLine(" --plaintext Specify file path to save decrypted contents.");
sb.AppendLine("NDV0 (Delta) options:");
sb.AppendLine(" Input delta patch can be a delta NCA file or a delta fragment file.");
sb.AppendLine(" --basefile Specify base file path.");
diff --git a/src/hactoolnet/ProcessNax0.cs b/src/hactoolnet/ProcessNax0.cs
new file mode 100644
index 00000000..61cd737c
--- /dev/null
+++ b/src/hactoolnet/ProcessNax0.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Linq;
+using System.Text;
+using LibHac;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.FsSystem;
+using static hactoolnet.Print;
+
+namespace hactoolnet
+{
+ internal static class ProcessNax0
+ {
+ public static void Process(Context ctx)
+ {
+ byte[][] keys = ctx.Keyset.SdCardKeys;
+
+ using var baseFile = new LocalFile(ctx.Options.InFile, OpenMode.Read);
+
+ AesXtsFile xtsFile = null;
+ int contentType = 0;
+
+ for (int i = 0; i < keys.Length; i++)
+ {
+ byte[] kekSource = keys[i].AsSpan(0, 0x10).ToArray();
+ byte[] validationKey = keys[i].AsSpan(0x10, 0x10).ToArray();
+
+ try
+ {
+ xtsFile = new AesXtsFile(OpenMode.Read, baseFile, ctx.Options.SdPath, kekSource, validationKey, 0x4000);
+ contentType = i;
+
+ break;
+ }
+ catch (HorizonResultException) { }
+ }
+
+ if (xtsFile == null)
+ {
+ ctx.Logger.LogMessage($"Error: NAX0 key derivation failed. Check SD card seed and relative path.");
+ return;
+ }
+
+ ctx.Logger.LogMessage(xtsFile.Print(contentType));
+
+ if (string.IsNullOrWhiteSpace(ctx.Options.PlaintextOut)) return;
+
+ xtsFile.AsStorage().WriteAllBytes(ctx.Options.PlaintextOut, ctx.Logger);
+ ctx.Logger.LogMessage($"Saved Decrypted NAX0 Content to {ctx.Options.PlaintextOut}...");
+ }
+
+ private static string Print(this AesXtsFile xtsFile, int contentType)
+ {
+ int colLen = 36;
+ var sb = new StringBuilder();
+ sb.AppendLine();
+
+ sb.AppendLine("NAX0:");
+
+ AesXtsFileHeader header = xtsFile.Header;
+ uint magic = header.Magic;
+
+ PrintItem(sb, colLen, " Magic:", Util.GetUtf8String(SpanHelpers.AsReadOnlyByteSpan(ref magic)));
+ PrintItem(sb, colLen, " Content Type:", GetContentType(contentType));
+ PrintItem(sb, colLen, " Content Size:", $"{header.Size:x12}");
+ PrintItem(sb, colLen, " Header HMAC:", header.Signature);
+ PrintItem(sb, colLen, " Encrypted Keys:", header.EncryptedKey1.Concat(header.EncryptedKey2).ToArray());
+ PrintItem(sb, colLen, " Decrypted Keys:", header.DecryptedKey1.Concat(header.DecryptedKey2).ToArray());
+
+ return sb.ToString();
+ }
+
+ private static string GetContentType(int type) => type switch
+ {
+ 0 => "Save",
+ 1 => "NCA",
+ 2 => "Custom storage",
+ _ => "Unknown"
+ };
+ }
+}
diff --git a/src/hactoolnet/Program.cs b/src/hactoolnet/Program.cs
index 7636229e..3d0514d7 100644
--- a/src/hactoolnet/Program.cs
+++ b/src/hactoolnet/Program.cs
@@ -126,6 +126,7 @@ namespace hactoolnet
ProcessFsBuild.ProcessRomFs(ctx);
break;
case FileType.Nax0:
+ ProcessNax0.Process(ctx);
break;
case FileType.SwitchFs:
ProcessSwitchFs.Process(ctx);