From 79b09267e6325d123d9b35ace8157226258fd773 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Mon, 2 Jul 2018 15:12:41 -0500 Subject: [PATCH] Begin command line tool --- hactoolnet/CliParser.cs | 128 ++++++++++++++++++++++++++++++++++++++++ hactoolnet/Options.cs | 38 ++++++++++++ hactoolnet/Program.cs | 64 +++++++++++++++++++- libhac/Keyset.cs | 2 + 4 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 hactoolnet/CliParser.cs create mode 100644 hactoolnet/Options.cs diff --git a/hactoolnet/CliParser.cs b/hactoolnet/CliParser.cs new file mode 100644 index 00000000..99540ccc --- /dev/null +++ b/hactoolnet/CliParser.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; + +namespace hactoolnet +{ + internal static class CliParser + { + private static readonly CliOption[] CliOptions = + { + new CliOption("intype", 't', 1, (o, a) => o.InFileType = ParseFileType(a[0])), + new CliOption("raw", 'r', 0, (o, a) => o.Raw = true), + new CliOption("keyset", 'k', 1, (o, a) => o.Keyfile = a[0]), + new CliOption("titlekeys", 1, (o, a) => o.TitleKeyFile = a[0]), + new CliOption("section0", 1, (o, a) => o.SectionOut[0] = a[0]), + new CliOption("section1", 1, (o, a) => o.SectionOut[1] = a[0]), + new CliOption("section2", 1, (o, a) => o.SectionOut[2] = a[0]), + new CliOption("section3", 1, (o, a) => o.SectionOut[3] = a[0]), + new CliOption("section0dir", 1, (o, a) => o.SectionOutDir[0] = a[0]), + new CliOption("section1dir", 1, (o, a) => o.SectionOutDir[1] = a[0]), + new CliOption("section2dir", 1, (o, a) => o.SectionOutDir[2] = a[0]), + new CliOption("section3dir", 1, (o, a) => o.SectionOutDir[3] = a[0]), + new CliOption("exefs", 1, (o, a) => o.ExefsOut = a[0]), + new CliOption("exefsdir", 1, (o, a) => o.ExefsOutDir = a[0]), + new CliOption("romfs", 1, (o, a) => o.RomfsOut = a[0]), + new CliOption("romfsdir", 1, (o, a) => o.RomfsOutDir = a[0]), + new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]), + new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]), + new CliOption("sdpath", 1, (o, a) => o.SdPath = a[0]), + }; + + public static Options Parse(string[] args) + { + var options = new Options(); + var inputSpecified = false; + + for (int i = 0; i < args.Length; i++) + { + string arg; + + if (args[i].Length == 2 && (args[i][0] == '-' || args[i][0] == '/')) + { + arg = args[i][1].ToString().ToLower(); + } + else if (args[i].Length > 2 && args[i].Substring(0, 2) == "--") + { + arg = args[i].Substring(2).ToLower(); + } + else + { + if (inputSpecified) + { + Console.WriteLine($"Unable to parse option {args[i]}"); + return null; + } + + options.InFile = args[i]; + inputSpecified = true; + continue; + } + + var option = CliOptions.FirstOrDefault(x => x.Long == arg || x.Short == arg); + if (option == null) + { + Console.WriteLine($"Unknown option {args[i]}"); + return null; + } + + if (i + option.ArgsNeeded >= args.Length) + { + Console.WriteLine($"Need {option.ArgsNeeded} parameter{(option.ArgsNeeded == 1 ? "" : "s")} after {args[i]}"); + return null; + } + + var optionArgs = new string[option.ArgsNeeded]; + Array.Copy(args, i + 1, optionArgs, 0, option.ArgsNeeded); + + option.Assigner(options, optionArgs); + i += option.ArgsNeeded; + } + + if (!inputSpecified) + { + Console.WriteLine("Input file must be specified"); + return null; + } + + return options; + } + + private static FileType ParseFileType(string input) + { + if (!Enum.TryParse(input, true, out FileType type)) + { + PrintWithUsage("Specified type is invalid."); + } + + return type; + } + + private static void PrintWithUsage(string toPrint) + { + Console.WriteLine(toPrint); + // PrintUsage(); + } + + private class CliOption + { + public CliOption(string longName, char shortName, int argsNeeded, Action assigner) + { + Long = longName; + Short = shortName.ToString(); + ArgsNeeded = argsNeeded; + Assigner = assigner; + } + public CliOption(string longName, int argsNeeded, Action assigner) + { + Long = longName; + ArgsNeeded = argsNeeded; + Assigner = assigner; + } + + public string Long { get; } + public string Short { get; } + public int ArgsNeeded { get; } + public Action Assigner { get; } + } + } +} diff --git a/hactoolnet/Options.cs b/hactoolnet/Options.cs new file mode 100644 index 00000000..90966995 --- /dev/null +++ b/hactoolnet/Options.cs @@ -0,0 +1,38 @@ +using libhac; + +namespace hactoolnet +{ + internal class Options + { + public string InFile; + public FileType InFileType = FileType.Nca; + public bool Raw = false; + public string Keyfile; + public string TitleKeyFile; + public string[] SectionOut = new string[4]; + public string[] SectionOutDir = new string[4]; + public string ExefsOut; + public string ExefsOutDir; + public string RomfsOut; + public string RomfsOutDir; + public string OutDir; + public string SdSeed; + public string SdPath; + } + + internal enum FileType + { + Nca, + Pfs0, + Romfs, + Nax0, + SwitchFs + } + + internal class Context + { + public Options Options; + public Keyset Keyset; + public IProgressReport Logger; + } +} diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index edd5a646..b0d9b27e 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -10,13 +10,73 @@ namespace hactoolnet { static void Main(string[] args) { - ListSdfs(args); + var ctx = new Context(); + ctx.Options = CliParser.Parse(args); + if (ctx.Options == null) return; + + using (var logger = new ProgressBar()) + { + ctx.Logger = logger; + OpenKeyset(ctx); + + switch (ctx.Options.InFileType) + { + case FileType.Nca: + ProcessNca(ctx); + break; + case FileType.Pfs0: + break; + case FileType.Romfs: + break; + case FileType.Nax0: + break; + case FileType.SwitchFs: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + //ListSdfs(args); //FileReadTest(args); //ReadNca(); //ListSdfs(args); //ReadNcaSdfs(args); } + 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.RomfsOut != null && nca.Sections[1] != null) + { + var romfs = nca.OpenSection(1, false); + + using (var outFile = new FileStream(ctx.Options.RomfsOut, FileMode.Create, FileAccess.ReadWrite)) + { + romfs.CopyStream(outFile, romfs.Length, ctx.Logger); + } + } + + if (ctx.Options.SectionOut[0] != null && nca.Sections[0] != null) + { + var romfs = nca.OpenSection(0, false); + + using (var outFile = new FileStream(ctx.Options.SectionOut[0], FileMode.Create, FileAccess.ReadWrite)) + { + romfs.CopyStream(outFile, romfs.Length, ctx.Logger); + } + } + } + } + + private static void OpenKeyset(Context ctx) + { + ctx.Keyset = ExternalKeys.ReadKeyFile(ctx.Options.Keyfile, ctx.Options.TitleKeyFile, ctx.Logger); + } + private static void ListSdfs(string[] args) { var sdfs = LoadSdFs(args); @@ -161,7 +221,6 @@ namespace hactoolnet { Console.WriteLine($"{app.Name} v{app.DisplayVersion}"); - long totalSize = 0; if (app.Main != null) { Console.WriteLine($"Software: {Util.GetBytesReadable(app.Main.GetSize())}"); @@ -189,3 +248,4 @@ namespace hactoolnet } } } + diff --git a/libhac/Keyset.cs b/libhac/Keyset.cs index 5b86532b..fa088f8f 100644 --- a/libhac/Keyset.cs +++ b/libhac/Keyset.cs @@ -84,6 +84,8 @@ namespace libhac public static Keyset ReadKeyFile(string filename, string titleKeysFilename, IProgressReport progress = null) { var keyset = ReadKeyFile(filename, progress); + if (titleKeysFilename == null) return keyset; + using (var reader = new StreamReader(new FileStream(titleKeysFilename, FileMode.Open, FileAccess.Read))) { string line;