2018-09-13 03:28:50 +02:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Reflection;
|
2018-10-03 22:14:23 +02:00
|
|
|
|
using System.Text;
|
2018-09-13 03:28:50 +02:00
|
|
|
|
using LibHac;
|
2018-11-19 05:20:34 +01:00
|
|
|
|
using LibHac.IO;
|
2018-10-03 22:14:23 +02:00
|
|
|
|
using static hactoolnet.Print;
|
2018-09-13 03:28:50 +02:00
|
|
|
|
|
|
|
|
|
namespace hactoolnet
|
|
|
|
|
{
|
|
|
|
|
internal static class ProcessNsp
|
|
|
|
|
{
|
2018-10-03 22:14:23 +02:00
|
|
|
|
public static void Process(Context ctx)
|
|
|
|
|
{
|
|
|
|
|
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
|
|
|
|
|
{
|
2018-11-19 05:20:34 +01:00
|
|
|
|
Pfs pfs = new Pfs(file.AsStorage());
|
2018-10-03 22:14:23 +02:00
|
|
|
|
ctx.Logger.LogMessage(pfs.Print());
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.OutDir != null)
|
|
|
|
|
{
|
|
|
|
|
pfs.Extract(ctx.Options.OutDir, ctx.Logger);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string Print(this Pfs pfs)
|
|
|
|
|
{
|
|
|
|
|
const int colLen = 36;
|
|
|
|
|
const int fileNameLen = 39;
|
|
|
|
|
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb.AppendLine();
|
|
|
|
|
|
|
|
|
|
sb.AppendLine("PFS0:");
|
|
|
|
|
|
|
|
|
|
PrintItem(sb, colLen, "Magic:", pfs.Header.Magic);
|
|
|
|
|
PrintItem(sb, colLen, "Number of files:", pfs.Header.NumFiles);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < pfs.Files.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
PfsFileEntry file = pfs.Files[i];
|
|
|
|
|
|
|
|
|
|
string label = i == 0 ? "Files:" : "";
|
|
|
|
|
string offsets = $"{file.Offset:x12}-{file.Offset + file.Size:x12}{file.HashValidity.GetValidityString()}";
|
|
|
|
|
string data = $"pfs0:/{file.Name}".PadRight(fileNameLen) + offsets;
|
|
|
|
|
|
|
|
|
|
PrintItem(sb, colLen, label, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-13 03:28:50 +02:00
|
|
|
|
public static void CreateNsp(Context ctx, SwitchFs switchFs)
|
|
|
|
|
{
|
2018-10-03 00:25:58 +02:00
|
|
|
|
ulong id = ctx.Options.TitleId;
|
2018-09-13 03:28:50 +02:00
|
|
|
|
if (id == 0)
|
|
|
|
|
{
|
|
|
|
|
ctx.Logger.LogMessage("Title ID must be specified to save title");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-03 00:25:58 +02:00
|
|
|
|
if (!switchFs.Titles.TryGetValue(id, out Title title))
|
2018-09-13 03:28:50 +02:00
|
|
|
|
{
|
|
|
|
|
ctx.Logger.LogMessage($"Could not find title {id:X16}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var builder = new Pfs0Builder();
|
|
|
|
|
|
2018-10-03 00:25:58 +02:00
|
|
|
|
foreach (Nca nca in title.Ncas)
|
2018-09-13 03:28:50 +02:00
|
|
|
|
{
|
2018-11-19 05:20:34 +01:00
|
|
|
|
builder.AddFile(nca.Filename, nca.GetStorage().AsStream());
|
2018-09-13 03:28:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
};
|
2018-10-03 00:25:58 +02:00
|
|
|
|
byte[] ticketBytes = ticket.GetBytes();
|
2018-09-13 03:28:50 +02:00
|
|
|
|
builder.AddFile($"{ticket.RightsId.ToHexString()}.tik", new MemoryStream(ticketBytes));
|
|
|
|
|
|
2018-10-03 00:25:58 +02:00
|
|
|
|
Assembly thisAssembly = Assembly.GetExecutingAssembly();
|
|
|
|
|
Stream cert = thisAssembly.GetManifestResourceStream("hactoolnet.CA00000003_XS00000020");
|
2018-09-13 03:28:50 +02:00
|
|
|
|
builder.AddFile($"{ticket.RightsId.ToHexString()}.cert", cert);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
using (var outStream = new FileStream(ctx.Options.NspOut, FileMode.Create, FileAccess.ReadWrite))
|
|
|
|
|
{
|
|
|
|
|
builder.Build(outStream, ctx.Logger);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|