diff --git a/hactoolnet/ProcessNca.cs b/hactoolnet/ProcessNca.cs new file mode 100644 index 00000000..a071fcfa --- /dev/null +++ b/hactoolnet/ProcessNca.cs @@ -0,0 +1,103 @@ +using System.IO; +using System.Linq; +using LibHac; + +namespace hactoolnet +{ + internal static class ProcessNca + { + public static void Process(Context ctx) + { + using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) + { + var nca = new Nca(ctx.Keyset, file, false); + + if (ctx.Options.BaseNca != null) + { + var baseFile = new FileStream(ctx.Options.BaseNca, FileMode.Open, FileAccess.Read); + var baseNca = new Nca(ctx.Keyset, baseFile, false); + nca.SetBaseNca(baseNca); + } + + for (int i = 0; i < 3; i++) + { + if (ctx.Options.SectionOut[i] != null) + { + nca.ExportSection(i, ctx.Options.SectionOut[i], ctx.Options.Raw, ctx.Logger); + } + + if (ctx.Options.SectionOutDir[i] != null) + { + nca.ExtractSection(i, ctx.Options.SectionOutDir[i], ctx.Logger); + } + + if (ctx.Options.Validate && nca.Sections[i] != null) + { + nca.VerifySection(i, ctx.Logger); + } + } + + if (ctx.Options.ListRomFs && nca.Sections[1] != null) + { + var romfs = new Romfs(nca.OpenSection(1, false)); + + foreach (var romfsFile in romfs.Files) + { + ctx.Logger.LogMessage(romfsFile.FullPath); + } + } + + if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null) + { + NcaSection section = nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs || x?.Type == SectionType.Bktr); + + if (section == null) + { + ctx.Logger.LogMessage("NCA has no RomFS section"); + return; + } + + if (section.Type == SectionType.Bktr && ctx.Options.BaseNca == null) + { + ctx.Logger.LogMessage("Cannot save BKTR section without base RomFS"); + return; + } + + 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 || ctx.Options.ExefsOut != null) + { + NcaSection section = nca.Sections.FirstOrDefault(x => x.IsExefs); + + if (section == null) + { + ctx.Logger.LogMessage("Could not find an ExeFS section"); + return; + } + + 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()); + } + } + } +} diff --git a/hactoolnet/ProcessNsp.cs b/hactoolnet/ProcessNsp.cs new file mode 100644 index 00000000..15510edf --- /dev/null +++ b/hactoolnet/ProcessNsp.cs @@ -0,0 +1,56 @@ +using System.IO; +using System.Reflection; +using LibHac; + +namespace hactoolnet +{ + internal static class ProcessNsp + { + public static void CreateNsp(Context ctx, SwitchFs switchFs) + { + var id = ctx.Options.TitleId; + if (id == 0) + { + ctx.Logger.LogMessage("Title ID must be specified to save title"); + return; + } + + if (!switchFs.Titles.TryGetValue(id, out var title)) + { + ctx.Logger.LogMessage($"Could not find title {id:X16}"); + return; + } + + var builder = new Pfs0Builder(); + + foreach (var nca in title.Ncas) + { + builder.AddFile(nca.Filename, nca.GetStream()); + } + + var ticket = new Ticket + { + SignatureType = TicketSigType.Rsa2048Sha256, + Signature = new byte[0x200], + Issuer = "Root-CA00000003-XS00000020", + FormatVersion = 2, + RightsId = title.MainNca.Header.RightsId, + TitleKeyBlock = title.MainNca.TitleKey, + CryptoType = title.MainNca.Header.CryptoType2, + SectHeaderOffset = 0x2C0 + }; + var ticketBytes = ticket.GetBytes(); + builder.AddFile($"{ticket.RightsId.ToHexString()}.tik", new MemoryStream(ticketBytes)); + + var thisAssembly = Assembly.GetExecutingAssembly(); + var cert = thisAssembly.GetManifestResourceStream("hactoolnet.CA00000003_XS00000020"); + builder.AddFile($"{ticket.RightsId.ToHexString()}.cert", cert); + + + using (var outStream = new FileStream(ctx.Options.NspOut, FileMode.Create, FileAccess.ReadWrite)) + { + builder.Build(outStream, ctx.Logger); + } + } + } +} diff --git a/hactoolnet/ProcessPackage.cs b/hactoolnet/ProcessPackage.cs new file mode 100644 index 00000000..ca696472 --- /dev/null +++ b/hactoolnet/ProcessPackage.cs @@ -0,0 +1,32 @@ +using System.IO; +using LibHac; + +namespace hactoolnet +{ + internal static class ProcessPackage + { + public static void ProcessPk11(Context ctx) + { + using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) + { + var package1 = new Package1(ctx.Keyset, file); + string outDir = ctx.Options.OutDir; + + if (outDir != null) + { + Directory.CreateDirectory(outDir); + + package1.Pk11.OpenWarmboot().WriteAllBytes(Path.Combine(outDir, "Warmboot.bin"), ctx.Logger); + package1.Pk11.OpenNxBootloader().WriteAllBytes(Path.Combine(outDir, "NX_Bootloader.bin"), ctx.Logger); + package1.Pk11.OpenSecureMonitor().WriteAllBytes(Path.Combine(outDir, "Secure_Monitor.bin"), ctx.Logger); + + using (var decFile = new FileStream(Path.Combine(outDir, "Decrypted.bin"), FileMode.Create)) + { + package1.OpenPackage1Ldr().CopyTo(decFile); + package1.Pk11.OpenDecryptedPk11().CopyTo(decFile); + } + } + } + } + } +} diff --git a/hactoolnet/ProcessRomfs.cs b/hactoolnet/ProcessRomfs.cs new file mode 100644 index 00000000..3bc88fd1 --- /dev/null +++ b/hactoolnet/ProcessRomfs.cs @@ -0,0 +1,42 @@ +using System.IO; +using LibHac; + +namespace hactoolnet +{ + internal static class ProcessRomfs + { + public static void Process(Context ctx) + { + using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) + { + var romfs = new Romfs(file); + Process(ctx, romfs); + } + } + + public static void Process(Context ctx, Romfs romfs) + { + if (ctx.Options.ListRomFs) + { + foreach (RomfsFile romfsFile in romfs.Files) + { + ctx.Logger.LogMessage(romfsFile.FullPath); + } + } + + if (ctx.Options.RomfsOut != null) + { + using (var outFile = new FileStream(ctx.Options.RomfsOut, FileMode.Create, FileAccess.ReadWrite)) + { + Stream romfsStream = romfs.OpenRawStream(); + romfsStream.CopyStream(outFile, romfsStream.Length, ctx.Logger); + } + } + + if (ctx.Options.RomfsOutDir != null) + { + romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger); + } + } + } +} diff --git a/hactoolnet/ProcessSwitchFs.cs b/hactoolnet/ProcessSwitchFs.cs new file mode 100644 index 00000000..065682bf --- /dev/null +++ b/hactoolnet/ProcessSwitchFs.cs @@ -0,0 +1,225 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using LibHac; +using LibHac.Savefile; + +namespace hactoolnet +{ + internal static class ProcessSwitchFs + { + public static void Process(Context ctx) + { + var switchFs = new SwitchFs(ctx.Keyset, new FileSystem(ctx.Options.InFile)); + + if (ctx.Options.ListTitles) + { + ListTitles(switchFs); + } + + if (ctx.Options.ListApps) + { + ctx.Logger.LogMessage(ListApplications(switchFs)); + } + + 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); + + 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) + { + ctx.Logger.LogMessage("Title ID must be specified to dump RomFS"); + 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?.Type == SectionType.Romfs || x?.Type == SectionType.Bktr); + + if (section == null) + { + ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no RomFS section"); + return; + } + + 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) + { + SaveTitle(ctx, switchFs); + } + + if (ctx.Options.NspOut != null) + { + ProcessNsp.CreateNsp(ctx, switchFs); + } + + if (ctx.Options.SaveOutDir != null) + { + ExportSdSaves(ctx, switchFs); + } + } + + private static void SaveTitle(Context ctx, SwitchFs switchFs) + { + var id = ctx.Options.TitleId; + if (id == 0) + { + ctx.Logger.LogMessage("Title ID must be specified to save title"); + return; + } + + if (!switchFs.Titles.TryGetValue(id, out var title)) + { + ctx.Logger.LogMessage($"Could not find title {id:X16}"); + return; + } + + var saveDir = Path.Combine(ctx.Options.OutDir, $"{title.Id:X16}v{title.Version.Version}"); + Directory.CreateDirectory(saveDir); + + foreach (var nca in title.Ncas) + { + var stream = nca.GetStream(); + var outFile = Path.Combine(saveDir, nca.Filename); + ctx.Logger.LogMessage(nca.Filename); + using (var outStream = new FileStream(outFile, FileMode.Create, FileAccess.ReadWrite)) + { + stream.CopyStream(outStream, stream.Length, ctx.Logger); + } + } + } + + static void ListTitles(SwitchFs sdfs) + { + foreach (var title in sdfs.Titles.Values.OrderBy(x => x.Id)) + { + Console.WriteLine($"{title.Name} {title.Control?.DisplayVersion}"); + Console.WriteLine($"{title.Id:X16} v{title.Version.Version} ({title.Version}) {title.Metadata.Type}"); + + foreach (var content in title.Metadata.ContentEntries) + { + Console.WriteLine( + $" {content.NcaId.ToHexString()}.nca {content.Type} {Util.GetBytesReadable(content.Size)}"); + } + + foreach (var nca in title.Ncas) + { + Console.WriteLine($" {nca.HasRightsId} {nca.NcaId} {nca.Header.ContentType}"); + + foreach (var sect in nca.Sections.Where(x => x != null)) + { + Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.CryptType} {sect.SuperblockHashValidity}"); + } + } + + Console.WriteLine(""); + } + } + + static string ListApplications(SwitchFs sdfs) + { + var sb = new StringBuilder(); + + foreach (var app in sdfs.Applications.Values.OrderBy(x => x.Name)) + { + sb.AppendLine($"{app.Name} v{app.DisplayVersion}"); + + if (app.Main != null) + { + sb.AppendLine($"Software: {Util.GetBytesReadable(app.Main.GetSize())}"); + } + + if (app.Patch != null) + { + sb.AppendLine($"Update Data: {Util.GetBytesReadable(app.Patch.GetSize())}"); + } + + if (app.AddOnContent.Count > 0) + { + sb.AppendLine($"DLC: {Util.GetBytesReadable(app.AddOnContent.Sum(x => x.GetSize()))}"); + } + + if (app.Nacp?.UserTotalSaveDataSize > 0) + sb.AppendLine($"User save: {Util.GetBytesReadable(app.Nacp.UserTotalSaveDataSize)}"); + if (app.Nacp?.DeviceTotalSaveDataSize > 0) + sb.AppendLine($"System save: {Util.GetBytesReadable(app.Nacp.DeviceTotalSaveDataSize)}"); + if (app.Nacp?.BcatDeliveryCacheStorageSize > 0) + sb.AppendLine($"BCAT save: {Util.GetBytesReadable(app.Nacp.BcatDeliveryCacheStorageSize)}"); + + sb.AppendLine(); + } + + 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); + } + } + } +} diff --git a/hactoolnet/ProcessXci.cs b/hactoolnet/ProcessXci.cs new file mode 100644 index 00000000..b2f11f8a --- /dev/null +++ b/hactoolnet/ProcessXci.cs @@ -0,0 +1,143 @@ +using System.IO; +using System.Linq; +using LibHac; + +namespace hactoolnet +{ + internal static class ProcessXci + { + public static void Process(Context ctx) + { + using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) + { + var xci = new Xci(ctx.Keyset, file); + + if (ctx.Options.RootDir != null) + { + xci.RootPartition?.Extract(ctx.Options.RootDir, ctx.Logger); + } + + if (ctx.Options.UpdateDir != null) + { + xci.UpdatePartition?.Extract(ctx.Options.UpdateDir, ctx.Logger); + } + + if (ctx.Options.NormalDir != null) + { + xci.NormalPartition?.Extract(ctx.Options.NormalDir, ctx.Logger); + } + + if (ctx.Options.SecureDir != null) + { + xci.SecurePartition?.Extract(ctx.Options.SecureDir, ctx.Logger); + } + + if (ctx.Options.LogoDir != null) + { + xci.LogoPartition?.Extract(ctx.Options.LogoDir, ctx.Logger); + } + + if (ctx.Options.OutDir != null && xci.RootPartition != null) + { + var root = xci.RootPartition; + if (root == null) + { + ctx.Logger.LogMessage("Could not find root partition"); + return; + } + + foreach (var sub in root.Files) + { + var subPfs = new Pfs(root.OpenFile(sub)); + var subDir = Path.Combine(ctx.Options.OutDir, sub.Name); + + subPfs.Extract(subDir, ctx.Logger); + } + } + + if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null) + { + var mainNca = GetXciMainNca(xci, ctx); + + if (mainNca == null) + { + ctx.Logger.LogMessage("Could not find Program NCA"); + return; + } + + var exefsSection = mainNca.Sections.FirstOrDefault(x => x.IsExefs); + + if (exefsSection == null) + { + 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"); + return; + } + + var romfsSection = mainNca.Sections.FirstOrDefault(x => x.Type == SectionType.Romfs); + + if (romfsSection == null) + { + ctx.Logger.LogMessage("NCA has no RomFS section"); + return; + } + + if (ctx.Options.RomfsOutDir != null) + { + var romfs = new Romfs(mainNca.OpenSection(romfsSection.SectionNum, false)); + romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger); + } + + if (ctx.Options.RomfsOut != null) + { + mainNca.ExportSection(romfsSection.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Logger); + } + } + } + } + + 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; + } + } +} diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index 12fc3d03..742abb70 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -1,7 +1,5 @@ using System; using System.IO; -using System.Linq; -using System.Reflection; using System.Text; using LibHac; using LibHac.Savefile; @@ -31,29 +29,29 @@ namespace hactoolnet switch (ctx.Options.InFileType) { case FileType.Nca: - ProcessNca(ctx); + ProcessNca.Process(ctx); break; case FileType.Pfs0: break; case FileType.Romfs: - ProcessRomFs(ctx); + ProcessRomfs.Process(ctx); break; case FileType.Nax0: break; case FileType.SwitchFs: - ProcessSwitchFs(ctx); + ProcessSwitchFs.Process(ctx); break; case FileType.Save: ProcessSave(ctx); break; case FileType.Xci: - ProcessXci(ctx); + ProcessXci.Process(ctx); break; case FileType.Keygen: ProcessKeygen(ctx); break; case FileType.Pk11: - ProcessPk11(ctx); + ProcessPackage.ProcessPk11(ctx); break; default: throw new ArgumentOutOfRangeException(); @@ -61,379 +59,6 @@ namespace hactoolnet } } - private static void ProcessNca(Context ctx) - { - using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) - { - var nca = new Nca(ctx.Keyset, file, false); - - if (ctx.Options.BaseNca != null) - { - var baseFile = new FileStream(ctx.Options.BaseNca, FileMode.Open, FileAccess.Read); - var baseNca = new Nca(ctx.Keyset, baseFile, false); - nca.SetBaseNca(baseNca); - } - - for (int i = 0; i < 3; i++) - { - if (ctx.Options.SectionOut[i] != null) - { - nca.ExportSection(i, ctx.Options.SectionOut[i], ctx.Options.Raw, ctx.Logger); - } - - if (ctx.Options.SectionOutDir[i] != null) - { - nca.ExtractSection(i, ctx.Options.SectionOutDir[i], ctx.Logger); - } - - if (ctx.Options.Validate && nca.Sections[i] != null) - { - nca.VerifySection(i, ctx.Logger); - } - } - - if (ctx.Options.ListRomFs && nca.Sections[1] != null) - { - var romfs = new Romfs(nca.OpenSection(1, false)); - - foreach (var romfsFile in romfs.Files) - { - ctx.Logger.LogMessage(romfsFile.FullPath); - } - } - - if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null) - { - NcaSection section = nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs || x?.Type == SectionType.Bktr); - - if (section == null) - { - ctx.Logger.LogMessage("NCA has no RomFS section"); - return; - } - - if (section.Type == SectionType.Bktr && ctx.Options.BaseNca == null) - { - ctx.Logger.LogMessage("Cannot save BKTR section without base RomFS"); - return; - } - - 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 || ctx.Options.ExefsOut != null) - { - NcaSection section = nca.Sections.FirstOrDefault(x => x.IsExefs); - - if (section == null) - { - ctx.Logger.LogMessage("Could not find an ExeFS section"); - return; - } - - 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()); - } - } - - private static void ProcessSwitchFs(Context ctx) - { - var switchFs = new SwitchFs(ctx.Keyset, new FileSystem(ctx.Options.InFile)); - - if (ctx.Options.ListTitles) - { - ListTitles(switchFs); - } - - if (ctx.Options.ListApps) - { - ctx.Logger.LogMessage(ListApplications(switchFs)); - } - - 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); - - 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) - { - ctx.Logger.LogMessage("Title ID must be specified to dump RomFS"); - 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?.Type == SectionType.Romfs || x?.Type == SectionType.Bktr); - - if (section == null) - { - ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no RomFS section"); - return; - } - - 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) - { - SaveTitle(ctx, switchFs); - } - - if (ctx.Options.NspOut != null) - { - CreateNsp(ctx, switchFs); - } - - if (ctx.Options.SaveOutDir != null) - { - ExportSdSaves(ctx, switchFs); - } - } - - private static void ProcessXci(Context ctx) - { - using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) - { - var xci = new Xci(ctx.Keyset, file); - - if (ctx.Options.RootDir != null) - { - xci.RootPartition?.Extract(ctx.Options.RootDir, ctx.Logger); - } - - if (ctx.Options.UpdateDir != null) - { - xci.UpdatePartition?.Extract(ctx.Options.UpdateDir, ctx.Logger); - } - - if (ctx.Options.NormalDir != null) - { - xci.NormalPartition?.Extract(ctx.Options.NormalDir, ctx.Logger); - } - - if (ctx.Options.SecureDir != null) - { - xci.SecurePartition?.Extract(ctx.Options.SecureDir, ctx.Logger); - } - - if (ctx.Options.LogoDir != null) - { - xci.LogoPartition?.Extract(ctx.Options.LogoDir, ctx.Logger); - } - - if (ctx.Options.OutDir != null && xci.RootPartition != null) - { - var root = xci.RootPartition; - if (root == null) - { - ctx.Logger.LogMessage("Could not find root partition"); - return; - } - - foreach (var sub in root.Files) - { - var subPfs = new Pfs(root.OpenFile(sub)); - var subDir = Path.Combine(ctx.Options.OutDir, sub.Name); - - subPfs.Extract(subDir, ctx.Logger); - } - } - - if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null) - { - var mainNca = GetXciMainNca(xci, ctx); - - if (mainNca == null) - { - ctx.Logger.LogMessage("Could not find Program NCA"); - return; - } - - var exefsSection = mainNca.Sections.FirstOrDefault(x => x.IsExefs); - - if (exefsSection == null) - { - 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"); - return; - } - - var romfsSection = mainNca.Sections.FirstOrDefault(x => x.Type == SectionType.Romfs); - - if (romfsSection == null) - { - ctx.Logger.LogMessage("NCA has no RomFS section"); - return; - } - - if (ctx.Options.RomfsOutDir != null) - { - var romfs = new Romfs(mainNca.OpenSection(romfsSection.SectionNum, false)); - romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger); - } - - if (ctx.Options.RomfsOut != null) - { - mainNca.ExportSection(romfsSection.SectionNum, ctx.Options.RomfsOut, ctx.Options.Raw, ctx.Logger); - } - } - } - } - - private static void ProcessRomFs(Context ctx) - { - using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) - { - var romfs = new Romfs(file); - ProcessRomFs(ctx, romfs); - } - } - - private static void ProcessRomFs(Context ctx, Romfs romfs) - { - if (ctx.Options.ListRomFs) - { - foreach (var romfsFile in romfs.Files) - { - ctx.Logger.LogMessage(romfsFile.FullPath); - } - } - - if (ctx.Options.RomfsOut != null) - { - using (var outFile = new FileStream(ctx.Options.RomfsOut, FileMode.Create, FileAccess.ReadWrite)) - { - var romfsStream = romfs.OpenRawStream(); - romfsStream.CopyStream(outFile, romfsStream.Length, ctx.Logger); - } - } - - if (ctx.Options.RomfsOutDir != null) - { - romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger); - } - } - - 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); @@ -512,185 +137,11 @@ namespace hactoolnet Console.WriteLine(ExternalKeys.PrintKeys(ctx.Keyset)); } - private static void ProcessPk11(Context ctx) - { - using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) - { - var package1 = new Package1(ctx.Keyset, file); - string outDir = ctx.Options.OutDir; - - if (outDir != null) - { - Directory.CreateDirectory(outDir); - - package1.Pk11.OpenWarmboot().WriteAllBytes(Path.Combine(outDir, "Warmboot.bin"), ctx.Logger); - package1.Pk11.OpenNxBootloader().WriteAllBytes(Path.Combine(outDir, "NX_Bootloader.bin"), ctx.Logger); - package1.Pk11.OpenSecureMonitor().WriteAllBytes(Path.Combine(outDir, "Secure_Monitor.bin"), ctx.Logger); - - using (var decFile = new FileStream(Path.Combine(outDir, "Decrypted.bin"), FileMode.Create)) - { - package1.OpenPackage1Ldr().CopyTo(decFile); - package1.Pk11.OpenDecryptedPk11().CopyTo(decFile); - } - } - } - } - // For running random stuff // ReSharper disable once UnusedParameter.Local private static void CustomTask(Context ctx) { } - - private static void SaveTitle(Context ctx, SwitchFs switchFs) - { - var id = ctx.Options.TitleId; - if (id == 0) - { - ctx.Logger.LogMessage("Title ID must be specified to save title"); - return; - } - - if (!switchFs.Titles.TryGetValue(id, out var title)) - { - ctx.Logger.LogMessage($"Could not find title {id:X16}"); - return; - } - - var saveDir = Path.Combine(ctx.Options.OutDir, $"{title.Id:X16}v{title.Version.Version}"); - Directory.CreateDirectory(saveDir); - - foreach (var nca in title.Ncas) - { - var stream = nca.GetStream(); - var outFile = Path.Combine(saveDir, nca.Filename); - ctx.Logger.LogMessage(nca.Filename); - using (var outStream = new FileStream(outFile, FileMode.Create, FileAccess.ReadWrite)) - { - stream.CopyStream(outStream, stream.Length, ctx.Logger); - } - } - } - - private static void CreateNsp(Context ctx, SwitchFs switchFs) - { - var id = ctx.Options.TitleId; - if (id == 0) - { - ctx.Logger.LogMessage("Title ID must be specified to save title"); - return; - } - - if (!switchFs.Titles.TryGetValue(id, out var title)) - { - ctx.Logger.LogMessage($"Could not find title {id:X16}"); - return; - } - - var builder = new Pfs0Builder(); - - foreach (var nca in title.Ncas) - { - builder.AddFile(nca.Filename, nca.GetStream()); - } - - var ticket = new Ticket - { - SignatureType = TicketSigType.Rsa2048Sha256, - Signature = new byte[0x200], - Issuer = "Root-CA00000003-XS00000020", - FormatVersion = 2, - RightsId = title.MainNca.Header.RightsId, - TitleKeyBlock = title.MainNca.TitleKey, - CryptoType = title.MainNca.Header.CryptoType2, - SectHeaderOffset = 0x2C0 - }; - var ticketBytes = ticket.GetBytes(); - builder.AddFile($"{ticket.RightsId.ToHexString()}.tik", new MemoryStream(ticketBytes)); - - var thisAssembly = Assembly.GetExecutingAssembly(); - var cert = thisAssembly.GetManifestResourceStream("hactoolnet.CA00000003_XS00000020"); - builder.AddFile($"{ticket.RightsId.ToHexString()}.cert", cert); - - - using (var outStream = new FileStream(ctx.Options.NspOut, FileMode.Create, FileAccess.ReadWrite)) - { - builder.Build(outStream, ctx.Logger); - } - } - - static void ListTitles(SwitchFs sdfs) - { - foreach (var title in sdfs.Titles.Values.OrderBy(x => x.Id)) - { - Console.WriteLine($"{title.Name} {title.Control?.DisplayVersion}"); - Console.WriteLine($"{title.Id:X16} v{title.Version.Version} ({title.Version}) {title.Metadata.Type}"); - - foreach (var content in title.Metadata.ContentEntries) - { - Console.WriteLine( - $" {content.NcaId.ToHexString()}.nca {content.Type} {Util.GetBytesReadable(content.Size)}"); - } - - foreach (var nca in title.Ncas) - { - Console.WriteLine($" {nca.HasRightsId} {nca.NcaId} {nca.Header.ContentType}"); - - foreach (var sect in nca.Sections.Where(x => x != null)) - { - Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.CryptType} {sect.SuperblockHashValidity}"); - } - } - - Console.WriteLine(""); - } - } - - static string ListApplications(SwitchFs sdfs) - { - var sb = new StringBuilder(); - - foreach (var app in sdfs.Applications.Values.OrderBy(x => x.Name)) - { - sb.AppendLine($"{app.Name} v{app.DisplayVersion}"); - - if (app.Main != null) - { - sb.AppendLine($"Software: {Util.GetBytesReadable(app.Main.GetSize())}"); - } - - if (app.Patch != null) - { - sb.AppendLine($"Update Data: {Util.GetBytesReadable(app.Patch.GetSize())}"); - } - - if (app.AddOnContent.Count > 0) - { - sb.AppendLine($"DLC: {Util.GetBytesReadable(app.AddOnContent.Sum(x => x.GetSize()))}"); - } - - if (app.Nacp?.UserTotalSaveDataSize > 0) - sb.AppendLine($"User save: {Util.GetBytesReadable(app.Nacp.UserTotalSaveDataSize)}"); - if (app.Nacp?.DeviceTotalSaveDataSize > 0) - sb.AppendLine($"System save: {Util.GetBytesReadable(app.Nacp.DeviceTotalSaveDataSize)}"); - if (app.Nacp?.BcatDeliveryCacheStorageSize > 0) - sb.AppendLine($"BCAT save: {Util.GetBytesReadable(app.Nacp.BcatDeliveryCacheStorageSize)}"); - - sb.AppendLine(); - } - - 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); - } - } } } -