mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Split hactoolnet processing into multiple files
This commit is contained in:
parent
7cef145513
commit
8230b0ba75
7 changed files with 606 additions and 554 deletions
103
hactoolnet/ProcessNca.cs
Normal file
103
hactoolnet/ProcessNca.cs
Normal file
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
hactoolnet/ProcessNsp.cs
Normal file
56
hactoolnet/ProcessNsp.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
hactoolnet/ProcessPackage.cs
Normal file
32
hactoolnet/ProcessPackage.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
hactoolnet/ProcessRomfs.cs
Normal file
42
hactoolnet/ProcessRomfs.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
225
hactoolnet/ProcessSwitchFs.cs
Normal file
225
hactoolnet/ProcessSwitchFs.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
143
hactoolnet/ProcessXci.cs
Normal file
143
hactoolnet/ProcessXci.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
using LibHac.Savefile;
|
using LibHac.Savefile;
|
||||||
|
@ -31,29 +29,29 @@ namespace hactoolnet
|
||||||
switch (ctx.Options.InFileType)
|
switch (ctx.Options.InFileType)
|
||||||
{
|
{
|
||||||
case FileType.Nca:
|
case FileType.Nca:
|
||||||
ProcessNca(ctx);
|
ProcessNca.Process(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Pfs0:
|
case FileType.Pfs0:
|
||||||
break;
|
break;
|
||||||
case FileType.Romfs:
|
case FileType.Romfs:
|
||||||
ProcessRomFs(ctx);
|
ProcessRomfs.Process(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Nax0:
|
case FileType.Nax0:
|
||||||
break;
|
break;
|
||||||
case FileType.SwitchFs:
|
case FileType.SwitchFs:
|
||||||
ProcessSwitchFs(ctx);
|
ProcessSwitchFs.Process(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Save:
|
case FileType.Save:
|
||||||
ProcessSave(ctx);
|
ProcessSave(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Xci:
|
case FileType.Xci:
|
||||||
ProcessXci(ctx);
|
ProcessXci.Process(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Keygen:
|
case FileType.Keygen:
|
||||||
ProcessKeygen(ctx);
|
ProcessKeygen(ctx);
|
||||||
break;
|
break;
|
||||||
case FileType.Pk11:
|
case FileType.Pk11:
|
||||||
ProcessPk11(ctx);
|
ProcessPackage.ProcessPk11(ctx);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
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)
|
private static void OpenKeyset(Context ctx)
|
||||||
{
|
{
|
||||||
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
@ -512,185 +137,11 @@ namespace hactoolnet
|
||||||
Console.WriteLine(ExternalKeys.PrintKeys(ctx.Keyset));
|
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
|
// For running random stuff
|
||||||
// ReSharper disable once UnusedParameter.Local
|
// ReSharper disable once UnusedParameter.Local
|
||||||
private static void CustomTask(Context ctx)
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue