mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add basic CLI functionality
This commit is contained in:
parent
79b09267e6
commit
20be7206a0
8 changed files with 267 additions and 40 deletions
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace hactoolnet
|
namespace hactoolnet
|
||||||
{
|
{
|
||||||
|
@ -26,6 +28,10 @@ namespace hactoolnet
|
||||||
new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]),
|
new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]),
|
||||||
new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]),
|
new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]),
|
||||||
new CliOption("sdpath", 1, (o, a) => o.SdPath = a[0]),
|
new CliOption("sdpath", 1, (o, a) => o.SdPath = a[0]),
|
||||||
|
new CliOption("listapps", 0, (o, a) => o.ListApps = true),
|
||||||
|
new CliOption("listtitles", 0, (o, a) => o.ListTitles = true),
|
||||||
|
new CliOption("listromfs", 0, (o, a) => o.ListRomFs = true),
|
||||||
|
new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Options Parse(string[] args)
|
public static Options Parse(string[] args)
|
||||||
|
@ -49,7 +55,7 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
if (inputSpecified)
|
if (inputSpecified)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Unable to parse option {args[i]}");
|
PrintWithUsage($"Unable to parse option {args[i]}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,13 +67,13 @@ namespace hactoolnet
|
||||||
var option = CliOptions.FirstOrDefault(x => x.Long == arg || x.Short == arg);
|
var option = CliOptions.FirstOrDefault(x => x.Long == arg || x.Short == arg);
|
||||||
if (option == null)
|
if (option == null)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Unknown option {args[i]}");
|
PrintWithUsage($"Unknown option {args[i]}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i + option.ArgsNeeded >= args.Length)
|
if (i + option.ArgsNeeded >= args.Length)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Need {option.ArgsNeeded} parameter{(option.ArgsNeeded == 1 ? "" : "s")} after {args[i]}");
|
PrintWithUsage($"Need {option.ArgsNeeded} parameter{(option.ArgsNeeded == 1 ? "" : "s")} after {args[i]}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +86,7 @@ namespace hactoolnet
|
||||||
|
|
||||||
if (!inputSpecified)
|
if (!inputSpecified)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Input file must be specified");
|
PrintWithUsage("Input file must be specified");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,12 +103,58 @@ namespace hactoolnet
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ulong ParseTitleId(string input)
|
||||||
|
{
|
||||||
|
if (input.Length != 16)
|
||||||
|
{
|
||||||
|
PrintWithUsage("Title ID must be 16 hex characters long");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ulong.TryParse(input, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var id))
|
||||||
|
{
|
||||||
|
PrintWithUsage("Could not parse title ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
private static void PrintWithUsage(string toPrint)
|
private static void PrintWithUsage(string toPrint)
|
||||||
{
|
{
|
||||||
Console.WriteLine(toPrint);
|
Console.WriteLine(toPrint);
|
||||||
|
Console.WriteLine(GetUsage());
|
||||||
// PrintUsage();
|
// PrintUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetUsage()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine("Usage: hactoolnet.exe [options...] <path>");
|
||||||
|
sb.AppendLine("Options:");
|
||||||
|
sb.AppendLine(" -r, --raw Keep raw data, don\'t unpack.");
|
||||||
|
sb.AppendLine(" -k, --keyset Load keys from an external file.");
|
||||||
|
sb.AppendLine(" -t, --intype=type Specify input file type [nca, switchfs]");
|
||||||
|
sb.AppendLine(" --titlekeys <file> Load title keys from an external file.");
|
||||||
|
sb.AppendLine("NCA options:");
|
||||||
|
sb.AppendLine(" --section0 <file> Specify Section 0 file path.");
|
||||||
|
sb.AppendLine(" --section1 <file> Specify Section 1 file path.");
|
||||||
|
sb.AppendLine(" --section2 <file> Specify Section 2 file path.");
|
||||||
|
sb.AppendLine(" --section3 <file> Specify Section 3 file path.");
|
||||||
|
sb.AppendLine(" --section0dir <dir> Specify Section 0 directory path.");
|
||||||
|
sb.AppendLine(" --section1dir <dir> Specify Section 1 directory path.");
|
||||||
|
sb.AppendLine(" --section2dir <dir> Specify Section 2 directory path.");
|
||||||
|
sb.AppendLine(" --section3dir <dir> Specify Section 3 directory path.");
|
||||||
|
sb.AppendLine(" --listromfs List files in RomFS.");
|
||||||
|
sb.AppendLine("Switch FS options:");
|
||||||
|
sb.AppendLine(" --sdseed <seed> 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 <title id> Specify title ID to use.");
|
||||||
|
sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path.");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
private class CliOption
|
private class CliOption
|
||||||
{
|
{
|
||||||
public CliOption(string longName, char shortName, int argsNeeded, Action<Options, string[]> assigner)
|
public CliOption(string longName, char shortName, int argsNeeded, Action<Options, string[]> assigner)
|
||||||
|
|
|
@ -18,6 +18,11 @@ namespace hactoolnet
|
||||||
public string OutDir;
|
public string OutDir;
|
||||||
public string SdSeed;
|
public string SdSeed;
|
||||||
public string SdPath;
|
public string SdPath;
|
||||||
|
public bool ListApps;
|
||||||
|
public bool ListTitles;
|
||||||
|
public bool ListRomFs;
|
||||||
|
public ulong TitleId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum FileType
|
internal enum FileType
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using libhac;
|
using libhac;
|
||||||
|
|
||||||
namespace hactoolnet
|
namespace hactoolnet
|
||||||
|
@ -10,6 +11,7 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
Console.OutputEncoding = Encoding.UTF8;
|
||||||
var ctx = new Context();
|
var ctx = new Context();
|
||||||
ctx.Options = CliParser.Parse(args);
|
ctx.Options = CliParser.Parse(args);
|
||||||
if (ctx.Options == null) return;
|
if (ctx.Options == null) return;
|
||||||
|
@ -31,6 +33,7 @@ namespace hactoolnet
|
||||||
case FileType.Nax0:
|
case FileType.Nax0:
|
||||||
break;
|
break;
|
||||||
case FileType.SwitchFs:
|
case FileType.SwitchFs:
|
||||||
|
ProcessSwitchFs(ctx);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
|
@ -50,31 +53,94 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
var nca = new Nca(ctx.Keyset, file, false);
|
var nca = new Nca(ctx.Keyset, file, false);
|
||||||
|
|
||||||
if (ctx.Options.RomfsOut != null && nca.Sections[1] != null)
|
for (int i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
var romfs = nca.OpenSection(1, false);
|
if (ctx.Options.SectionOut[i] != null)
|
||||||
|
{
|
||||||
|
nca.ExportSection(i, ctx.Options.SectionOut[i], ctx.Options.Raw, ctx.Logger);
|
||||||
|
}
|
||||||
|
|
||||||
using (var outFile = new FileStream(ctx.Options.RomfsOut, FileMode.Create, FileAccess.ReadWrite))
|
if (ctx.Options.SectionOutDir[i] != null)
|
||||||
{
|
{
|
||||||
romfs.CopyStream(outFile, romfs.Length, ctx.Logger);
|
nca.ExtractSection(i, ctx.Options.SectionOutDir[i], ctx.Logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.Options.SectionOut[0] != null && nca.Sections[0] != null)
|
if (ctx.Options.ListRomFs && nca.Sections[1] != null)
|
||||||
{
|
{
|
||||||
var romfs = nca.OpenSection(0, false);
|
var romfs = new Romfs(nca.OpenSection(1, false));
|
||||||
|
|
||||||
using (var outFile = new FileStream(ctx.Options.SectionOut[0], FileMode.Create, FileAccess.ReadWrite))
|
foreach (var romfsFile in romfs.Files)
|
||||||
{
|
{
|
||||||
romfs.CopyStream(outFile, romfs.Length, ctx.Logger);
|
ctx.Logger.LogMessage(romfsFile.FullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ProcessSwitchFs(Context ctx)
|
||||||
|
{
|
||||||
|
var switchFs = new SdFs(ctx.Keyset, ctx.Options.InFile);
|
||||||
|
|
||||||
|
if (ctx.Options.ListTitles)
|
||||||
|
{
|
||||||
|
ListTitles(switchFs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.Options.ListApps)
|
||||||
|
{
|
||||||
|
ctx.Logger.LogMessage(ListApplications(switchFs));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.Options.RomfsOutDir != 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.ProgramNca == null)
|
||||||
|
{
|
||||||
|
ctx.Logger.LogMessage($"Could not find main program data for title {id:X16}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var romfs = new Romfs(title.ProgramNca.OpenSection(1, false));
|
||||||
|
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void OpenKeyset(Context ctx)
|
private static void OpenKeyset(Context ctx)
|
||||||
{
|
{
|
||||||
ctx.Keyset = ExternalKeys.ReadKeyFile(ctx.Options.Keyfile, ctx.Options.TitleKeyFile, ctx.Logger);
|
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
var homeKeyFile = Path.Combine(home, ".switch", "prod.keys");
|
||||||
|
var homeTitleKeyFile = Path.Combine(home, ".switch", "titlekeys.txt");
|
||||||
|
var keyFile = ctx.Options.Keyfile;
|
||||||
|
var titleKeyFile = ctx.Options.TitleKeyFile;
|
||||||
|
|
||||||
|
if (keyFile == null && File.Exists(homeKeyFile))
|
||||||
|
{
|
||||||
|
keyFile = homeKeyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (titleKeyFile == null && File.Exists(homeTitleKeyFile))
|
||||||
|
{
|
||||||
|
titleKeyFile = homeTitleKeyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Keyset = ExternalKeys.ReadKeyFile(keyFile, titleKeyFile, ctx.Logger);
|
||||||
|
if (ctx.Options.SdSeed != null)
|
||||||
|
{
|
||||||
|
ctx.Keyset.SetSdSeed(ctx.Options.SdSeed.ToBytes());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ListSdfs(string[] args)
|
private static void ListSdfs(string[] args)
|
||||||
|
@ -215,36 +281,40 @@ namespace hactoolnet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ListApplications(SdFs sdfs)
|
static string ListApplications(SdFs sdfs)
|
||||||
{
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
foreach (var app in sdfs.Applications.Values.OrderBy(x => x.Name))
|
foreach (var app in sdfs.Applications.Values.OrderBy(x => x.Name))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{app.Name} v{app.DisplayVersion}");
|
sb.AppendLine($"{app.Name} v{app.DisplayVersion}");
|
||||||
|
|
||||||
if (app.Main != null)
|
if (app.Main != null)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Software: {Util.GetBytesReadable(app.Main.GetSize())}");
|
sb.AppendLine($"Software: {Util.GetBytesReadable(app.Main.GetSize())}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.Patch != null)
|
if (app.Patch != null)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Update Data: {Util.GetBytesReadable(app.Patch.GetSize())}");
|
sb.AppendLine($"Update Data: {Util.GetBytesReadable(app.Patch.GetSize())}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.AddOnContent.Count > 0)
|
if (app.AddOnContent.Count > 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"DLC: {Util.GetBytesReadable(app.AddOnContent.Sum(x => x.GetSize()))}");
|
sb.AppendLine($"DLC: {Util.GetBytesReadable(app.AddOnContent.Sum(x => x.GetSize()))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.Nacp?.UserTotalSaveDataSize > 0)
|
if (app.Nacp?.UserTotalSaveDataSize > 0)
|
||||||
Console.WriteLine($"User save: {Util.GetBytesReadable(app.Nacp.UserTotalSaveDataSize)}");
|
sb.AppendLine($"User save: {Util.GetBytesReadable(app.Nacp.UserTotalSaveDataSize)}");
|
||||||
if (app.Nacp?.DeviceTotalSaveDataSize > 0)
|
if (app.Nacp?.DeviceTotalSaveDataSize > 0)
|
||||||
Console.WriteLine($"System save: {Util.GetBytesReadable(app.Nacp.DeviceTotalSaveDataSize)}");
|
sb.AppendLine($"System save: {Util.GetBytesReadable(app.Nacp.DeviceTotalSaveDataSize)}");
|
||||||
if (app.Nacp?.BcatSaveDataSize > 0)
|
if (app.Nacp?.BcatSaveDataSize > 0)
|
||||||
Console.WriteLine($"BCAT save: {Util.GetBytesReadable(app.Nacp.BcatSaveDataSize)}");
|
sb.AppendLine($"BCAT save: {Util.GetBytesReadable(app.Nacp.BcatSaveDataSize)}");
|
||||||
|
|
||||||
Console.WriteLine("");
|
sb.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,8 @@ namespace libhac
|
||||||
public static Keyset ReadKeyFile(string filename, IProgressReport progress = null)
|
public static Keyset ReadKeyFile(string filename, IProgressReport progress = null)
|
||||||
{
|
{
|
||||||
var keyset = new Keyset();
|
var keyset = new Keyset();
|
||||||
|
if (filename == null) return keyset;
|
||||||
|
|
||||||
using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
|
using (var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read)))
|
||||||
{
|
{
|
||||||
string line;
|
string line;
|
||||||
|
|
|
@ -207,4 +207,46 @@ namespace libhac
|
||||||
|
|
||||||
public Pfs0Superblock Pfs0 { get; set; }
|
public Pfs0Superblock Pfs0 { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class NcaExtensions
|
||||||
|
{
|
||||||
|
public static void ExportSection(this Nca nca, int index, string filename, bool raw = false, IProgressReport logger = null)
|
||||||
|
{
|
||||||
|
if(index < 0 || index > 3) throw new IndexOutOfRangeException();
|
||||||
|
if (nca.Sections[index] == null) return;
|
||||||
|
|
||||||
|
var section = nca.OpenSection(index, raw);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(filename));
|
||||||
|
|
||||||
|
using (var outFile = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite))
|
||||||
|
{
|
||||||
|
section.CopyStream(outFile, section.Length, logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ExtractSection(this Nca nca, int index, string outputDir, IProgressReport logger = null)
|
||||||
|
{
|
||||||
|
if(index < 0 || index > 3) throw new IndexOutOfRangeException();
|
||||||
|
if (nca.Sections[index] == null) return;
|
||||||
|
|
||||||
|
var section = nca.Sections[index];
|
||||||
|
var stream = nca.OpenSection(index, false);
|
||||||
|
|
||||||
|
switch (section.Type)
|
||||||
|
{
|
||||||
|
case SectionType.Invalid:
|
||||||
|
break;
|
||||||
|
case SectionType.Pfs0:
|
||||||
|
var pfs0 = new Pfs0(stream);
|
||||||
|
pfs0.Extract(outputDir, logger);
|
||||||
|
break;
|
||||||
|
case SectionType.Romfs:
|
||||||
|
var romfs = new Romfs(stream);
|
||||||
|
romfs.Extract(outputDir, logger);
|
||||||
|
break;
|
||||||
|
case SectionType.Bktr:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using System.IO;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace libhac
|
namespace libhac
|
||||||
|
@ -7,7 +9,9 @@ namespace libhac
|
||||||
{
|
{
|
||||||
public Pfs0Header Header { get; set; }
|
public Pfs0Header Header { get; set; }
|
||||||
public int HeaderSize { get; set; }
|
public int HeaderSize { get; set; }
|
||||||
public Pfs0FileEntry[] Entries { get; set; }
|
public Pfs0FileEntry[] Files { get; set; }
|
||||||
|
|
||||||
|
private Dictionary<string, Pfs0FileEntry> FileDict { get; }
|
||||||
private Stream Stream { get; set; }
|
private Stream Stream { get; set; }
|
||||||
|
|
||||||
public Pfs0(Stream stream)
|
public Pfs0(Stream stream)
|
||||||
|
@ -25,26 +29,42 @@ namespace libhac
|
||||||
{
|
{
|
||||||
reader.BaseStream.Position = 16;
|
reader.BaseStream.Position = 16;
|
||||||
|
|
||||||
Entries = new Pfs0FileEntry[Header.NumFiles];
|
Files = new Pfs0FileEntry[Header.NumFiles];
|
||||||
for (int i = 0; i < Header.NumFiles; i++)
|
for (int i = 0; i < Header.NumFiles; i++)
|
||||||
{
|
{
|
||||||
Entries[i] = new Pfs0FileEntry(reader) { Index = i };
|
Files[i] = new Pfs0FileEntry(reader) { Index = i };
|
||||||
}
|
}
|
||||||
|
|
||||||
int stringTableOffset = 16 + 24 * Header.NumFiles;
|
int stringTableOffset = 16 + 24 * Header.NumFiles;
|
||||||
for (int i = 0; i < Header.NumFiles; i++)
|
for (int i = 0; i < Header.NumFiles; i++)
|
||||||
{
|
{
|
||||||
reader.BaseStream.Position = stringTableOffset + Entries[i].StringTableOffset;
|
reader.BaseStream.Position = stringTableOffset + Files[i].StringTableOffset;
|
||||||
Entries[i].Name = reader.ReadAsciiZ();
|
Files[i].Name = reader.ReadAsciiZ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileDict = Files.ToDictionary(x => x.Name, x => x);
|
||||||
Stream = stream;
|
Stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Stream OpenFile(string filename)
|
||||||
|
{
|
||||||
|
if (!FileDict.TryGetValue(filename, out Pfs0FileEntry file))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return OpenFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream OpenFile(Pfs0FileEntry file)
|
||||||
|
{
|
||||||
|
return new SubStream(Stream, HeaderSize + file.Offset, file.Size);
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] GetFile(int index)
|
public byte[] GetFile(int index)
|
||||||
{
|
{
|
||||||
var entry = Entries[index];
|
var entry = Files[index];
|
||||||
var file = new byte[entry.Size];
|
var file = new byte[entry.Size];
|
||||||
Stream.Position = HeaderSize + entry.Offset;
|
Stream.Position = HeaderSize + entry.Offset;
|
||||||
Stream.Read(file, 0, file.Length);
|
Stream.Read(file, 0, file.Length);
|
||||||
|
@ -108,4 +128,23 @@ namespace libhac
|
||||||
Reserved = reader.ReadUInt32();
|
Reserved = reader.ReadUInt32();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Pfs0Extensions
|
||||||
|
{
|
||||||
|
public static void Extract(this Pfs0 pfs0, string outDir, IProgressReport logger = null)
|
||||||
|
{
|
||||||
|
foreach (var file in pfs0.Files)
|
||||||
|
{
|
||||||
|
var stream = pfs0.OpenFile(file);
|
||||||
|
var outName = Path.Combine(outDir, file.Name);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(outName));
|
||||||
|
|
||||||
|
using (var outFile = new FileStream(outName, FileMode.Create, FileAccess.ReadWrite))
|
||||||
|
{
|
||||||
|
logger?.LogMessage(file.Name);
|
||||||
|
stream.CopyStream(outFile, stream.Length, logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace libhac
|
||||||
{
|
{
|
||||||
public class Romfs
|
public class Romfs
|
||||||
{
|
{
|
||||||
public static readonly int IvfcMaxLevel = 6;
|
internal const int IvfcMaxLevel = 6;
|
||||||
public RomfsHeader Header { get; }
|
public RomfsHeader Header { get; }
|
||||||
public List<RomfsDir> Directories { get; } = new List<RomfsDir>();
|
public List<RomfsDir> Directories { get; } = new List<RomfsDir>();
|
||||||
public List<RomfsFile> Files { get; } = new List<RomfsFile>();
|
public List<RomfsFile> Files { get; } = new List<RomfsFile>();
|
||||||
|
@ -62,13 +62,17 @@ namespace libhac
|
||||||
|
|
||||||
public Stream OpenFile(string filename)
|
public Stream OpenFile(string filename)
|
||||||
{
|
{
|
||||||
if (!FileDict.TryGetValue(filename, out var file))
|
if (!FileDict.TryGetValue(filename, out RomfsFile file))
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var stream = new SubStream(Stream, Header.DataOffset + file.DataOffset, file.DataLength);
|
return OpenFile(file);
|
||||||
return stream;
|
}
|
||||||
|
|
||||||
|
public Stream OpenFile(RomfsFile file)
|
||||||
|
{
|
||||||
|
return new SubStream(Stream, Header.DataOffset + file.DataOffset, file.DataLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetFile(string filename)
|
public byte[] GetFile(string filename)
|
||||||
|
@ -232,4 +236,23 @@ namespace libhac
|
||||||
public ulong HashBlockSize { get; set; }
|
public ulong HashBlockSize { get; set; }
|
||||||
public ulong HashBlockCount { get; set; }
|
public ulong HashBlockCount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class RomfsExtensions
|
||||||
|
{
|
||||||
|
public static void Extract(this Romfs romfs, string outDir, IProgressReport logger = null)
|
||||||
|
{
|
||||||
|
foreach (var file in romfs.Files)
|
||||||
|
{
|
||||||
|
var stream = romfs.OpenFile(file);
|
||||||
|
var outName = outDir + file.FullPath;
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(outName));
|
||||||
|
|
||||||
|
using (var outFile = new FileStream(outName, FileMode.Create, FileAccess.ReadWrite))
|
||||||
|
{
|
||||||
|
logger?.LogMessage(file.FullPath);
|
||||||
|
stream.CopyStream(outFile, stream.Length, logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,6 @@ namespace libhac
|
||||||
public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>();
|
public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>();
|
||||||
public Dictionary<ulong, Application> Applications { get; } = new Dictionary<ulong, Application>();
|
public Dictionary<ulong, Application> Applications { get; } = new Dictionary<ulong, Application>();
|
||||||
|
|
||||||
private List<Nax0> Nax0s { get; } = new List<Nax0>();
|
|
||||||
|
|
||||||
public SdFs(Keyset keyset, string rootDir)
|
public SdFs(Keyset keyset, string rootDir)
|
||||||
{
|
{
|
||||||
RootDir = rootDir;
|
RootDir = rootDir;
|
||||||
|
@ -63,7 +61,6 @@ namespace libhac
|
||||||
{
|
{
|
||||||
var sdPath = "/" + Util.GetRelativePath(file, ContentsDir).Replace('\\', '/');
|
var sdPath = "/" + Util.GetRelativePath(file, ContentsDir).Replace('\\', '/');
|
||||||
var nax0 = new Nax0(Keyset, stream, sdPath, false);
|
var nax0 = new Nax0(Keyset, stream, sdPath, false);
|
||||||
Nax0s.Add(nax0);
|
|
||||||
nca = new Nca(Keyset, nax0.Stream, false);
|
nca = new Nca(Keyset, nax0.Stream, false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -212,9 +209,6 @@ namespace libhac
|
||||||
nca.Dispose();
|
nca.Dispose();
|
||||||
}
|
}
|
||||||
Ncas.Clear();
|
Ncas.Clear();
|
||||||
|
|
||||||
// Disposing the Nca disposes the Nax0 as well
|
|
||||||
Nax0s.Clear();
|
|
||||||
Titles.Clear();
|
Titles.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue