mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
hactoolnet: Improve XCI and PFS0 handling
- Automatically import title keys from tickets. - Automatically apply updates when reading an XCI or NSP. - Allow listing all contained NCAs, programs or applications. - Allow specifying the title when an XCI or NSP contains more than one. - Allow extracting ExeFS or RomFS directly from an NSP.
This commit is contained in:
parent
a002d92b13
commit
4362b7be53
8 changed files with 243 additions and 144 deletions
|
@ -74,11 +74,16 @@ XCI options:
|
|||
--securedir <dir> Specify secure XCI directory path.
|
||||
--logodir <dir> Specify logo XCI directory path.
|
||||
--outdir <dir> Specify XCI directory path.
|
||||
--nspout <file> Specify file for the created NSP.
|
||||
Partition FS and XCI options:
|
||||
--exefs <file> Specify main ExeFS file path.
|
||||
--exefsdir <dir> Specify main ExeFS directory path.
|
||||
--romfs <file> Specify main RomFS file path.
|
||||
--romfsdir <dir> Specify main RomFS directory path.
|
||||
--nspout <file> Specify file for the created NSP.
|
||||
--listapps List application info.
|
||||
--listtitles List title info for all titles.
|
||||
--listncas List info for all NCAs.
|
||||
--title <title id> Specify title ID to use.
|
||||
Package1 options:
|
||||
--outdir <dir> Specify Package1 directory path.
|
||||
Package2 options:
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
namespace LibHac.Ncm;
|
||||
using System;
|
||||
|
||||
namespace LibHac.Ncm;
|
||||
|
||||
public enum ContentType : byte
|
||||
{
|
||||
|
@ -24,11 +26,13 @@ public enum ContentMetaType : byte
|
|||
Delta = 0x83
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ContentMetaAttribute : byte
|
||||
{
|
||||
None = 0,
|
||||
IncludesExFatDriver = 1,
|
||||
Rebootless = 2
|
||||
IncludesExFatDriver = 1 << 0,
|
||||
Rebootless = 1 << 1,
|
||||
Compacted = 1 << 2,
|
||||
}
|
||||
|
||||
public enum UpdateType : byte
|
||||
|
|
|
@ -15,6 +15,7 @@ public class Cnmt
|
|||
public int TableOffset { get; }
|
||||
public int ContentEntryCount { get; }
|
||||
public int MetaEntryCount { get; }
|
||||
public ContentMetaAttribute ContentMetaAttributes { get; }
|
||||
|
||||
public CnmtContentEntry[] ContentEntries { get; }
|
||||
public CnmtContentMetaEntry[] MetaEntries { get; }
|
||||
|
@ -42,11 +43,12 @@ public class Cnmt
|
|||
TableOffset = reader.ReadUInt16();
|
||||
ContentEntryCount = reader.ReadUInt16();
|
||||
MetaEntryCount = reader.ReadUInt16();
|
||||
ContentMetaAttributes = (ContentMetaAttribute)reader.ReadByte();
|
||||
|
||||
// Old, pre-release cnmt files don't have the "required system version" field.
|
||||
// Try to detect this by reading the padding after that field.
|
||||
// The old format usually contains hashes there.
|
||||
file.Position += 8;
|
||||
file.Position += 7;
|
||||
int padding = reader.ReadInt32();
|
||||
bool isOldCnmtFormat = padding != 0;
|
||||
|
||||
|
|
|
@ -249,11 +249,16 @@ internal static class CliParser
|
|||
sb.AppendLine(" --securedir <dir> Specify secure XCI directory path.");
|
||||
sb.AppendLine(" --logodir <dir> Specify logo XCI directory path.");
|
||||
sb.AppendLine(" --outdir <dir> Specify XCI directory path.");
|
||||
sb.AppendLine(" --nspout <file> Specify file for the created NSP.");
|
||||
sb.AppendLine("Partition FS and XCI options:");
|
||||
sb.AppendLine(" --exefs <file> Specify main ExeFS file path.");
|
||||
sb.AppendLine(" --exefsdir <dir> Specify main ExeFS directory path.");
|
||||
sb.AppendLine(" --romfs <file> Specify main RomFS file path.");
|
||||
sb.AppendLine(" --romfsdir <dir> Specify main RomFS directory path.");
|
||||
sb.AppendLine(" --nspout <file> Specify file for the created NSP.");
|
||||
sb.AppendLine(" --listapps List application info.");
|
||||
sb.AppendLine(" --listtitles List title info for all titles.");
|
||||
sb.AppendLine(" --listncas List info for all NCAs.");
|
||||
sb.AppendLine(" --title <title id> Specify title ID to use.");
|
||||
sb.AppendLine("Package1 options:");
|
||||
sb.AppendLine(" --outdir <dir> Specify Package1 directory path.");
|
||||
sb.AppendLine("Package2 options:");
|
||||
|
|
145
src/hactoolnet/ProcessAppFs.cs
Normal file
145
src/hactoolnet/ProcessAppFs.cs
Normal file
|
@ -0,0 +1,145 @@
|
|||
using System.Linq;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Spl;
|
||||
using LibHac.Tools.Es;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
|
||||
namespace hactoolnet;
|
||||
|
||||
internal static class ProcessAppFs
|
||||
{
|
||||
private static void ImportTickets(Context ctx, IFileSystem fileSystem)
|
||||
{
|
||||
foreach (DirectoryEntryEx entry in fileSystem.EnumerateEntries("*.tik", SearchOptions.Default))
|
||||
{
|
||||
using var tikFile = new UniqueRef<IFile>();
|
||||
fileSystem.OpenFile(ref tikFile.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
var ticket = new Ticket(tikFile.Get.AsStream());
|
||||
|
||||
if (ticket.TitleKeyType != TitleKeyType.Common)
|
||||
continue;
|
||||
|
||||
if (ticket.RightsId.IsZeros())
|
||||
continue;
|
||||
|
||||
var rightsId = SpanHelpers.AsStruct<RightsId>(ticket.RightsId);
|
||||
var accessKey = SpanHelpers.AsStruct<AccessKey>(ticket.TitleKeyBlock);
|
||||
|
||||
ctx.KeySet.ExternalKeySet.Add(rightsId, accessKey).ThrowIfFailure();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Process(Context ctx, IFileSystem fileSystem)
|
||||
{
|
||||
ImportTickets(ctx, fileSystem);
|
||||
|
||||
SwitchFs switchFs = SwitchFs.OpenNcaDirectory(ctx.KeySet, fileSystem);
|
||||
|
||||
if (ctx.Options.ListNcas)
|
||||
{
|
||||
ctx.Logger.LogMessage(ProcessSwitchFs.ListNcas(switchFs));
|
||||
}
|
||||
|
||||
if (ctx.Options.ListTitles)
|
||||
{
|
||||
ctx.Logger.LogMessage(ProcessSwitchFs.ListTitles(switchFs));
|
||||
}
|
||||
|
||||
if (ctx.Options.ListApps)
|
||||
{
|
||||
ctx.Logger.LogMessage(ProcessSwitchFs.ListApplications(switchFs));
|
||||
}
|
||||
|
||||
ulong id = GetTargetProgramId(ctx, switchFs);
|
||||
if (id == ulong.MaxValue)
|
||||
{
|
||||
ctx.Logger.LogMessage("Title ID must be specified to dump ExeFS or RomFS");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!switchFs.Titles.TryGetValue(id, out Title 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;
|
||||
}
|
||||
|
||||
if (title.Metadata?.ContentMetaAttributes.HasFlag(ContentMetaAttribute.Compacted) == true)
|
||||
{
|
||||
ctx.Logger.LogMessage($"Cannot extract compacted NCAs");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null)
|
||||
{
|
||||
if (!title.MainNca.Nca.SectionExists(NcaSectionType.Code))
|
||||
{
|
||||
ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no ExeFS section");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.Options.ExefsOutDir != null)
|
||||
{
|
||||
IFileSystem fs = title.MainNca.OpenFileSystem(NcaSectionType.Code, ctx.Options.IntegrityLevel);
|
||||
fs.Extract(ctx.Options.ExefsOutDir, ctx.Logger);
|
||||
}
|
||||
|
||||
if (ctx.Options.ExefsOut != null)
|
||||
{
|
||||
title.MainNca.Nca.ExportSection(NcaSectionType.Code, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null || ctx.Options.ListRomFs)
|
||||
{
|
||||
if (!title.MainNca.Nca.SectionExists(NcaSectionType.Data))
|
||||
{
|
||||
ctx.Logger.LogMessage($"Main NCA for title {id:X16} has no RomFS section");
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessRomfs.Process(ctx, title.MainNca.OpenStorage(NcaSectionType.Data, ctx.Options.IntegrityLevel));
|
||||
}
|
||||
|
||||
if (ctx.Options.NspOut != null)
|
||||
{
|
||||
ProcessPfs.CreateNsp(ctx, switchFs);
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong GetTargetProgramId(Context ctx, SwitchFs switchFs)
|
||||
{
|
||||
ulong id = ctx.Options.TitleId;
|
||||
if (id != 0)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
if (switchFs.Applications.Count != 1)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
ulong applicationId = switchFs.Applications.Values.First().TitleId;
|
||||
ulong updateId = applicationId | 0x800ul;
|
||||
|
||||
if (switchFs.Titles.ContainsKey(updateId))
|
||||
{
|
||||
return updateId;
|
||||
}
|
||||
|
||||
return applicationId;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using LibHac.Fs;
|
||||
|
@ -24,6 +25,11 @@ internal static class ProcessPfs
|
|||
{
|
||||
pfs.Extract(ctx.Options.OutDir, ctx.Logger);
|
||||
}
|
||||
|
||||
if (pfs.EnumerateEntries("*.nca", SearchOptions.Default).Any())
|
||||
{
|
||||
ProcessAppFs.Process(ctx, pfs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -234,7 +234,7 @@ internal static class ProcessSwitchFs
|
|||
}
|
||||
}
|
||||
|
||||
static string ListTitles(SwitchFs sdfs)
|
||||
public static string ListTitles(SwitchFs sdfs)
|
||||
{
|
||||
var table = new TableBuilder("Title ID", "Version", "", "Type", "Size", "Display Version", "Name");
|
||||
|
||||
|
@ -252,7 +252,7 @@ internal static class ProcessSwitchFs
|
|||
return table.Print();
|
||||
}
|
||||
|
||||
static string ListNcas(SwitchFs sdfs)
|
||||
public static string ListNcas(SwitchFs sdfs)
|
||||
{
|
||||
var table = new TableBuilder("NCA ID", "Type", "Title ID");
|
||||
|
||||
|
@ -264,7 +264,7 @@ internal static class ProcessSwitchFs
|
|||
return table.Print();
|
||||
}
|
||||
|
||||
static string ListApplications(SwitchFs sdfs)
|
||||
public static string ListApplications(SwitchFs sdfs)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ using LibHac.FsSystem;
|
|||
using LibHac.Gc.Impl;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
|
||||
namespace hactoolnet;
|
||||
|
||||
|
@ -65,78 +64,11 @@ internal static class ProcessXci
|
|||
}
|
||||
}
|
||||
|
||||
if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null)
|
||||
if (xci.HasPartition(XciPartitionType.Secure))
|
||||
{
|
||||
Nca mainNca = GetXciMainNca(xci, ctx);
|
||||
|
||||
if (mainNca == null)
|
||||
{
|
||||
ctx.Logger.LogMessage("Could not find Program NCA");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mainNca.SectionExists(NcaSectionType.Code))
|
||||
{
|
||||
ctx.Logger.LogMessage("NCA has no ExeFS section");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.Options.ExefsOutDir != null)
|
||||
{
|
||||
mainNca.ExtractSection(NcaSectionType.Code, ctx.Options.ExefsOutDir, ctx.Options.IntegrityLevel, ctx.Logger);
|
||||
}
|
||||
|
||||
if (ctx.Options.ExefsOut != null)
|
||||
{
|
||||
mainNca.ExportSection(NcaSectionType.Code, ctx.Options.ExefsOut, ctx.Options.Raw, ctx.Options.IntegrityLevel, ctx.Logger);
|
||||
ProcessAppFs.Process(ctx, xci.OpenPartition(XciPartitionType.Secure));
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null || ctx.Options.ListRomFs)
|
||||
{
|
||||
Nca mainNca = GetXciMainNca(xci, ctx);
|
||||
|
||||
if (mainNca == null)
|
||||
{
|
||||
ctx.Logger.LogMessage("Could not find Program NCA");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mainNca.SectionExists(NcaSectionType.Data))
|
||||
{
|
||||
ctx.Logger.LogMessage("NCA has no RomFS section");
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessRomfs.Process(ctx, mainNca.OpenStorage(NcaSectionType.Data, ctx.Options.IntegrityLevel, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Nca GetXciMainNca(Xci xci, Context ctx)
|
||||
{
|
||||
XciPartition partition = xci.OpenPartition(XciPartitionType.Secure);
|
||||
|
||||
if (partition == null)
|
||||
{
|
||||
ctx.Logger.LogMessage("Could not find secure partition");
|
||||
return null;
|
||||
}
|
||||
|
||||
Nca mainNca = null;
|
||||
|
||||
foreach (PartitionFileEntry fileEntry in partition.Files.Where(x => x.Name.EndsWith(".nca")))
|
||||
{
|
||||
IStorage ncaStorage = partition.OpenFile(fileEntry, OpenMode.Read).AsStorage();
|
||||
var nca = new Nca(ctx.KeySet, ncaStorage);
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
{
|
||||
mainNca = nca;
|
||||
}
|
||||
}
|
||||
|
||||
return mainNca;
|
||||
}
|
||||
|
||||
private static string Print(this Xci xci)
|
||||
|
|
Loading…
Reference in a new issue