LibHac/hactoolnet/CliParser.cs

230 lines
11 KiB
C#
Raw Normal View History

2018-07-02 22:12:41 +02:00
using System;
2018-07-03 04:21:35 +02:00
using System.Globalization;
2018-07-02 22:12:41 +02:00
using System.Linq;
2018-07-03 04:21:35 +02:00
using System.Text;
2018-07-02 22:12:41 +02:00
namespace hactoolnet
{
internal static class CliParser
{
private static readonly CliOption[] CliOptions =
{
2018-07-05 23:37:30 +02:00
new CliOption("custom", 0, (o, a) => o.RunCustom = true),
2018-07-02 22:12:41 +02:00
new CliOption("intype", 't', 1, (o, a) => o.InFileType = ParseFileType(a[0])),
new CliOption("raw", 'r', 0, (o, a) => o.Raw = true),
2018-07-05 23:37:30 +02:00
new CliOption("verify", 'y', 0, (o, a) => o.Validate = true),
2018-07-02 22:12:41 +02:00
new CliOption("keyset", 'k', 1, (o, a) => o.Keyfile = a[0]),
new CliOption("titlekeys", 1, (o, a) => o.TitleKeyFile = a[0]),
2018-07-09 18:47:32 +02:00
new CliOption("consolekeys", 1, (o, a) => o.ConsoleKeyFile = a[0]),
2018-07-02 22:12:41 +02:00
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]),
2018-07-24 21:51:52 +02:00
new CliOption("debugoutdir", 1, (o, a) => o.DebugOutDir = a[0]),
2018-09-11 05:09:35 +02:00
new CliOption("savedir", 1, (o, a) => o.SaveOutDir = a[0]),
2018-07-02 22:12:41 +02:00
new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]),
new CliOption("nspout", 1, (o, a) => o.NspOut = a[0]),
2018-07-02 22:12:41 +02:00
new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]),
new CliOption("sdpath", 1, (o, a) => o.SdPath = a[0]),
2018-07-07 22:45:06 +02:00
new CliOption("basenca", 1, (o, a) => o.BaseNca = a[0]),
new CliOption("rootdir", 1, (o, a) => o.RootDir = a[0]),
new CliOption("updatedir", 1, (o, a) => o.UpdateDir = a[0]),
new CliOption("normaldir", 1, (o, a) => o.NormalDir = a[0]),
new CliOption("securedir", 1, (o, a) => o.SecureDir = a[0]),
new CliOption("logodir", 1, (o, a) => o.LogoDir = a[0]),
2018-07-03 04:21:35 +02:00
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),
2018-09-16 05:31:06 +02:00
new CliOption("sign", 0, (o, a) => o.SignSave = true),
2018-07-03 04:21:35 +02:00
new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])),
2018-07-02 22:12:41 +02:00
};
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)
{
2018-07-03 04:21:35 +02:00
PrintWithUsage($"Unable to parse option {args[i]}");
2018-07-02 22:12:41 +02:00
return null;
}
options.InFile = args[i];
inputSpecified = true;
continue;
}
var option = CliOptions.FirstOrDefault(x => x.Long == arg || x.Short == arg);
if (option == null)
{
2018-07-03 04:21:35 +02:00
PrintWithUsage($"Unknown option {args[i]}");
2018-07-02 22:12:41 +02:00
return null;
}
if (i + option.ArgsNeeded >= args.Length)
{
2018-07-03 04:21:35 +02:00
PrintWithUsage($"Need {option.ArgsNeeded} parameter{(option.ArgsNeeded == 1 ? "" : "s")} after {args[i]}");
2018-07-02 22:12:41 +02:00
return null;
}
var optionArgs = new string[option.ArgsNeeded];
Array.Copy(args, i + 1, optionArgs, 0, option.ArgsNeeded);
option.Assigner(options, optionArgs);
i += option.ArgsNeeded;
}
2018-09-01 21:23:45 +02:00
if (!inputSpecified && options.InFileType != FileType.Keygen && !options.RunCustom)
2018-07-02 22:12:41 +02:00
{
2018-07-03 04:21:35 +02:00
PrintWithUsage("Input file must be specified");
2018-07-02 22:12:41 +02:00
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;
}
2018-07-03 04:21:35 +02:00
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;
}
2018-07-02 22:12:41 +02:00
private static void PrintWithUsage(string toPrint)
{
Console.WriteLine(toPrint);
2018-07-03 04:21:35 +02:00
Console.WriteLine(GetUsage());
2018-07-02 22:12:41 +02:00
// PrintUsage();
}
2018-07-03 04:21:35 +02:00
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.");
2018-07-05 23:37:30 +02:00
sb.AppendLine(" -y, --verify Verify hashes.");
2018-07-03 04:21:35 +02:00
sb.AppendLine(" -k, --keyset Load keys from an external file.");
2018-09-18 02:52:22 +02:00
sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, ini1, kip1, switchfs, save, keygen]");
2018-07-03 04:21:35 +02:00
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(" --exefs <file> Specify ExeFS directory path.");
sb.AppendLine(" --exefsdir <dir> Specify ExeFS directory path.");
sb.AppendLine(" --romfs <file> Specify RomFS directory path.");
sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path.");
2018-07-03 04:21:35 +02:00
sb.AppendLine(" --listromfs List files in RomFS.");
2018-07-08 18:40:55 +02:00
sb.AppendLine(" --basenca Set Base NCA to use with update partitions.");
2018-09-13 02:51:21 +02:00
sb.AppendLine("RomFS options:");
sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path.");
sb.AppendLine(" --listromfs List files in RomFS.");
sb.AppendLine("XCI options:");
sb.AppendLine(" --rootdir <dir> Specify root XCI directory path.");
sb.AppendLine(" --updatedir <dir> Specify update XCI directory path.");
sb.AppendLine(" --normaldir <dir> Specify normal XCI directory path.");
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(" --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.");
2018-08-15 01:38:32 +02:00
sb.AppendLine(" --romfsdir <dir> Specify main RomFS directory path.");
2018-09-10 03:48:17 +02:00
sb.AppendLine(" --nspout <file> Specify file for the created NSP.");
2018-09-13 02:51:21 +02:00
sb.AppendLine("Package1 options:");
sb.AppendLine(" --outdir <dir> Specify Package1 directory path.");
2018-09-17 03:26:16 +02:00
sb.AppendLine("Package2 options:");
sb.AppendLine(" --outdir <dir> Specify Package2 directory path.");
2018-09-18 02:52:22 +02:00
sb.AppendLine("INI1 options:");
sb.AppendLine(" --outdir <dir> Specify INI1 directory path.");
2018-07-03 04:21:35 +02:00
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(" --outdir <dir> Specify directory path to save title NCAs to. (--title must be specified)");
sb.AppendLine(" --exefs <file> Specify ExeFS directory path. (--title must be specified)");
sb.AppendLine(" --exefsdir <dir> Specify ExeFS directory path. (--title must be specified)");
sb.AppendLine(" --romfs <file> Specify RomFS directory path. (--title must be specified)");
2018-07-20 01:34:16 +02:00
sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path. (--title must be specified)");
2018-09-11 05:09:35 +02:00
sb.AppendLine(" --savedir <dir> Specify save file directory path.");
2018-07-24 21:51:52 +02:00
sb.AppendLine("Savefile options:");
sb.AppendLine(" --outdir <dir> Specify directory path to save contents to.");
sb.AppendLine(" --debugoutdir <dir> Specify directory path to save intermediate data to for debugging.");
2018-09-16 05:31:06 +02:00
sb.AppendLine(" --sign Sign the save file. (Requires device_key in key file)");
2018-07-24 21:51:52 +02:00
2018-07-03 04:21:35 +02:00
return sb.ToString();
}
2018-07-02 22:12:41 +02:00
private class CliOption
{
public CliOption(string longName, char shortName, int argsNeeded, Action<Options, string[]> assigner)
{
Long = longName;
Short = shortName.ToString();
ArgsNeeded = argsNeeded;
Assigner = assigner;
}
public CliOption(string longName, int argsNeeded, Action<Options, string[]> assigner)
{
Long = longName;
ArgsNeeded = argsNeeded;
Assigner = assigner;
}
public string Long { get; }
public string Short { get; }
public int ArgsNeeded { get; }
public Action<Options, string[]> Assigner { get; }
}
}
}