diff --git a/hactoolnet/CliParser.cs b/hactoolnet/CliParser.cs
index c097b771..afa00c40 100644
--- a/hactoolnet/CliParser.cs
+++ b/hactoolnet/CliParser.cs
@@ -156,7 +156,10 @@ namespace hactoolnet
sb.AppendLine(" --section1dir
Specify Section 1 directory path.");
sb.AppendLine(" --section2dir Specify Section 2 directory path.");
sb.AppendLine(" --section3dir Specify Section 3 directory path.");
- sb.AppendLine(" --romfsdir Specify main RomFS directory path.");
+ sb.AppendLine(" --exefs Specify ExeFS directory path.");
+ sb.AppendLine(" --exefsdir Specify ExeFS directory path.");
+ sb.AppendLine(" --romfs Specify RomFS directory path.");
+ sb.AppendLine(" --romfsdir Specify RomFS directory path.");
sb.AppendLine(" --listromfs List files in RomFS.");
sb.AppendLine(" --basenca Set Base NCA to use with update partitions.");
sb.AppendLine("XCI options:");
@@ -166,13 +169,19 @@ namespace hactoolnet
sb.AppendLine(" --securedir Specify secure XCI directory path.");
sb.AppendLine(" --logodir Specify logo XCI directory path.");
sb.AppendLine(" --outdir Specify XCI directory path.");
+ sb.AppendLine(" --exefs Specify main ExeFS file path.");
+ sb.AppendLine(" --exefsdir Specify main ExeFS directory path.");
+ sb.AppendLine(" --romfs Specify main RomFS file path.");
sb.AppendLine(" --romfsdir Specify main RomFS directory path.");
sb.AppendLine("Switch FS options:");
sb.AppendLine(" --sdseed Set console unique seed for SD card NAX0 encryption.");
sb.AppendLine(" --listapps List application info.");
sb.AppendLine(" --listtitles List title info for all titles.");
sb.AppendLine(" --title Specify title ID to use.");
- sb.AppendLine(" --outdir Specify directory path to save title to. (--title must be specified)");
+ sb.AppendLine(" --outdir Specify directory path to save title NCAs to. (--title must be specified)");
+ sb.AppendLine(" --exefs Specify ExeFS directory path. (--title must be specified)");
+ sb.AppendLine(" --exefsdir Specify ExeFS directory path. (--title must be specified)");
+ sb.AppendLine(" --romfs Specify RomFS directory path. (--title must be specified)");
sb.AppendLine(" --romfsdir Specify RomFS directory path. (--title must be specified)");
sb.AppendLine("Savefile options:");
sb.AppendLine(" --outdir Specify directory path to save contents to.");
diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs
index 95239873..823193d0 100644
--- a/hactoolnet/Program.cs
+++ b/hactoolnet/Program.cs
@@ -95,7 +95,7 @@ namespace hactoolnet
}
}
- if (ctx.Options.RomfsOutDir != null)
+ if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null)
{
NcaSection section = nca.Sections.FirstOrDefault(x => x.Type == SectionType.Romfs || x.Type == SectionType.Bktr);
@@ -105,27 +105,25 @@ namespace hactoolnet
return;
}
- if (section.Type == SectionType.Bktr)
+ if (section.Type == SectionType.Bktr && ctx.Options.BaseNca == null)
{
- if (ctx.Options.BaseNca == null)
- {
- ctx.Logger.LogMessage("Cannot save BKTR section without base RomFS");
- return;
- }
-
- var bktr = nca.OpenSection(1, false);
- var romfs = new Romfs(bktr);
- romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
-
+ ctx.Logger.LogMessage("Cannot save BKTR section without base RomFS");
+ return;
}
- else
+
+ if (ctx.Options.RomfsOut != null)
+ {
+ nca.ExportSection(section.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Logger);
+ }
+
+ if (ctx.Options.RomfsOutDir != null)
{
var romfs = new Romfs(nca.OpenSection(section.SectionNum, false));
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
}
}
- if (ctx.Options.ExefsOutDir != null)
+ if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null)
{
NcaSection section = nca.Sections.FirstOrDefault(x => x.IsExefs);
@@ -135,7 +133,15 @@ namespace hactoolnet
return;
}
- nca.ExtractSection(section.SectionNum, ctx.Options.ExefsOutDir, ctx.Logger);
+ if (ctx.Options.ExefsOut != null)
+ {
+ nca.ExportSection(section.SectionNum, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Logger);
+ }
+
+ if (ctx.Options.ExefsOutDir != null)
+ {
+ nca.ExtractSection(section.SectionNum, ctx.Options.ExefsOutDir, ctx.Logger);
+ }
}
ctx.Logger.LogMessage(nca.Dump());
@@ -156,7 +162,47 @@ namespace hactoolnet
ctx.Logger.LogMessage(ListApplications(switchFs));
}
- if (ctx.Options.RomfsOutDir != null)
+ if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null)
+ {
+ var id = ctx.Options.TitleId;
+ if (id == 0)
+ {
+ ctx.Logger.LogMessage("Title ID must be specified to dump ExeFS");
+ return;
+ }
+
+ if (!switchFs.Titles.TryGetValue(id, out var title))
+ {
+ ctx.Logger.LogMessage($"Could not find title {id:X16}");
+ return;
+ }
+
+ if (title.MainNca == null)
+ {
+ ctx.Logger.LogMessage($"Could not find main data for title {id:X16}");
+ return;
+ }
+
+ var section = title.MainNca.Sections.FirstOrDefault(x => x.IsExefs == true);
+
+ if (section == null)
+ {
+ ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no ExeFS section");
+ return;
+ }
+
+ if (ctx.Options.ExefsOutDir != null)
+ {
+ title.MainNca.ExtractSection(section.SectionNum, ctx.Options.ExefsOutDir, ctx.Logger);
+ }
+
+ if (ctx.Options.ExefsOut != null)
+ {
+ title.MainNca.ExportSection(section.SectionNum, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Logger);
+ }
+ }
+
+ if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null)
{
var id = ctx.Options.TitleId;
if (id == 0)
@@ -181,12 +227,20 @@ namespace hactoolnet
if (section == null)
{
- ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no Rom FS section");
+ ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no RomFS section");
return;
}
- var romfs = new Romfs(title.MainNca.OpenSection(section.SectionNum, false));
- romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
+ if (ctx.Options.RomfsOutDir != null)
+ {
+ var romfs = new Romfs(title.MainNca.OpenSection(section.SectionNum, false));
+ romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
+ }
+
+ if (ctx.Options.RomfsOut != null)
+ {
+ title.MainNca.ExportSection(section.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Logger);
+ }
}
if (ctx.Options.OutDir != null)
@@ -249,27 +303,39 @@ namespace hactoolnet
}
}
- if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null)
+ if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null)
{
- if (xci.SecurePartition == null)
+ var mainNca = GetXciMainNca(xci, ctx);
+
+ if (mainNca == null)
{
- ctx.Logger.LogMessage("Could not find secure partition");
+ ctx.Logger.LogMessage("Could not find Program NCA");
return;
}
- Nca mainNca = null;
+ var exefsSection = mainNca.Sections.FirstOrDefault(x => x.IsExefs == true);
- foreach (var fileEntry in xci.SecurePartition.Files.Where(x => x.Name.EndsWith(".nca")))
+ if (exefsSection == null)
{
- var ncaStream = xci.SecurePartition.OpenFile(fileEntry);
- var nca = new Nca(ctx.Keyset, ncaStream, true);
-
- if (nca.Header.ContentType == ContentType.Program)
- {
- mainNca = nca;
- }
+ ctx.Logger.LogMessage("NCA has no ExeFS section");
+ return;
}
+ if (ctx.Options.ExefsOutDir != null)
+ {
+ mainNca.ExtractSection(exefsSection.SectionNum, ctx.Options.ExefsOutDir, ctx.Logger);
+ }
+
+ if (ctx.Options.ExefsOut != null)
+ {
+ mainNca.ExportSection(exefsSection.SectionNum, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Logger);
+ }
+ }
+
+ if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null)
+ {
+ var mainNca = GetXciMainNca(xci, ctx);
+
if (mainNca == null)
{
ctx.Logger.LogMessage("Could not find Program NCA");
@@ -298,6 +364,30 @@ namespace hactoolnet
}
}
+ private static Nca GetXciMainNca(Xci xci, Context ctx)
+ {
+ if (xci.SecurePartition == null)
+ {
+ ctx.Logger.LogMessage("Could not find secure partition");
+ return null;
+ }
+
+ Nca mainNca = null;
+
+ foreach (var fileEntry in xci.SecurePartition.Files.Where(x => x.Name.EndsWith(".nca")))
+ {
+ var ncaStream = xci.SecurePartition.OpenFile(fileEntry);
+ var nca = new Nca(ctx.Keyset, ncaStream, true);
+
+ if (nca.Header.ContentType == ContentType.Program)
+ {
+ mainNca = nca;
+ }
+ }
+
+ return mainNca;
+ }
+
private static void OpenKeyset(Context ctx)
{
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);