123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.IO.Abstractions;
- using System.Linq;
- using System.Text;
- using CommandLine;
- using COM3D2.Toolkit.Arc;
- using COM3D2.Toolkit.Arc.Files;
- using Ganss.IO;
- namespace ArcToolkitCLI
- {
- interface IOptions
- {
- [Value(0, MetaName = "input", HelpText = "Input ARC files")]
- IEnumerable<string> Input { get; set; }
- }
- interface IOutputOptions
- {
- [Option('o', "output", HelpText = "Output directory", Default = ".")]
- string Output { get; set; }
- }
- interface IDecryptionOptions
- {
- [Option("arc-search-dir", Required = false,
- HelpText = "Directory where to search ARC file to decrypt the WARP file")]
- string ArcDirectory { get; set; }
- [Option('k', "key", HelpText = "Decryption key as a base64 string (applied to all inputs)", Required = false)]
- string DecryptionKey { get; set; }
- [Option("key-file",
- HelpText =
- "A file with decryption keys on each line. Format of the file is <decryption arc name>:<key in base64>.",
- Required = false)]
- string KeyFile { get; set; }
- [Option("warc", Required = false, HelpText = "WARC file to use for decryption")]
- string WarcFile { get; set; }
- }
- [Verb("extract", HelpText = "Extract the contents of the given ARC files")]
- class ExtractOptions : IOptions, IDecryptionOptions, IOutputOptions
- {
- public string ArcDirectory { get; set; }
- public string DecryptionKey { get; set; }
- public string KeyFile { get; set; }
- public string WarcFile { get; set; }
- public IEnumerable<string> Input { get; set; }
- public string Output { get; set; }
- }
- [Verb("info", HelpText = "Display information about the given ARC files")]
- class InfoOptions : IOptions
- {
- [Option("only-key",
- HelpText = "If the archive is a WARC file, output only the decryption key as a base64 string",
- Required = false)]
- public bool OnlyKey { get; set; }
- public IEnumerable<string> Input { get; set; }
- }
- [Verb("decrypt", HelpText = "Decrypts the provided WARP files")]
- class DecryptOptions : IOptions, IDecryptionOptions, IOutputOptions
- {
- public string ArcDirectory { get; set; }
- public string DecryptionKey { get; set; }
- public string KeyFile { get; set; }
- public string WarcFile { get; set; }
- public IEnumerable<string> Input { get; set; }
- public string Output { get; set; }
- }
- static class Errors
- {
- public enum ErrorCodes
- {
- NotAFile = 100,
- UnknownArcType,
- KeyNotFound,
- InvalidKeyFile,
- NoCorrectKey
- }
- public static readonly string[] ErrorMessages =
- {
- "{0} is not a file",
- "{0} is not a known ARC file",
- "No key specified or the key file does not exist",
- "The provided keyfile is not valid",
- "The ARC {0} needs a key from {1}"
- };
- public static int Error(ErrorCodes code, params object[] args)
- {
- Console.Error.WriteLine(ErrorMessages[code - ErrorCodes.NotAFile], args);
- return (int) code;
- }
- }
- static class Util
- {
- static readonly char[] GlobCharacters = {'*', '{', '}', '[', ']', '?'};
- public static IEnumerable<FileSystemInfoBase> EnumerateFiles(IEnumerable<string> patterns)
- {
- foreach (var pattern in patterns)
- if (IsGlob(pattern))
- foreach (var fileSystemInfoBase in Glob.Expand(pattern))
- yield return fileSystemInfoBase;
- else
- yield return new FileInfoWrapper(new FileSystem(), new FileInfo(pattern));
- }
- static bool IsGlob(string path) { return path.IndexOfAny(GlobCharacters) >= 0; }
- }
- class Program
- {
- static int Decrypt(DecryptOptions opts)
- {
- var errCode = GetDecryptionKeys(opts, out var keysDict);
- if (errCode != 0)
- return errCode;
- Directory.CreateDirectory(opts.Output);
- foreach (var file in Util.EnumerateFiles(opts.Input))
- {
- if (!file.Exists)
- return Errors.Error(Errors.ErrorCodes.NotAFile, file.Name);
- using (var stream = File.OpenRead(file.FullName))
- {
- var header = new byte[4];
- stream.Read(header, 0, header.Length);
- var headerString = Encoding.ASCII.GetString(header);
- stream.Position = 0;
- if (headerString == "warc")
- Console.WriteLine($"{file.Name} is a WARC file, skipping...");
- else if (headerString == "warp")
- try
- {
- var arcStream = WarpArc.DecryptWarp(stream, requestedFile =>
- {
- if (keysDict.TryGetValue("*", out var key))
- return key;
- if (keysDict.TryGetValue(requestedFile, out key))
- return key;
- if (Directory.Exists(opts.ArcDirectory) &&
- File.Exists(Path.Combine(opts.ArcDirectory, requestedFile)))
- return ReadKeyFromFile(Path.Combine(opts.ArcDirectory, requestedFile));
- if (File.Exists(opts.WarcFile))
- return ReadKeyFromFile(opts.WarcFile);
- throw new FileNotFoundException("No key found for the requested ARC", requestedFile);
- });
- Console.WriteLine(
- $"Writing {Path.Combine(opts.Output, file.Name)} with length {arcStream.Length}");
- using (var output = File.Create(Path.Combine(opts.Output, file.Name)))
- arcStream.CopyTo(output);
- }
- catch (FileNotFoundException fe)
- {
- return Errors.Error(Errors.ErrorCodes.NoCorrectKey, file.Name, fe.FileName);
- }
- else
- return Errors.Error(Errors.ErrorCodes.UnknownArcType, file.Name);
- }
- }
- return 0;
- }
- static int DisplayInfo(InfoOptions opts)
- {
- var first = true;
- foreach (var file in Util.EnumerateFiles(opts.Input))
- {
- if (!file.Exists)
- return Errors.Error(Errors.ErrorCodes.NotAFile, file.Name);
- if (!first && !opts.OnlyKey)
- Console.WriteLine();
- first = false;
- using (var stream = File.OpenRead(file.FullName))
- {
- var header = new byte[4];
- stream.Read(header, 0, header.Length);
- var headerString = Encoding.ASCII.GetString(header);
- stream.Position = 0;
- if (headerString == "warc")
- {
- var key = new byte[2048];
- stream.Read(key, 0, key.Length);
- var keyBase64 = Convert.ToBase64String(key);
- if (opts.OnlyKey)
- {
- Console.WriteLine($"{Path.GetFileName(file.FullName)}:{keyBase64}");
- continue;
- }
- stream.Position = 0;
- using (var warc = new WarcArc(stream))
- {
- Console.WriteLine($"File name: {file.Name}");
- Console.WriteLine("ARC type: WARC");
- Console.WriteLine($"ARC Name: {warc.Name}");
- Console.WriteLine($"File count: {warc.Entries.Count()}");
- Console.WriteLine($"Decryption key: {keyBase64}");
- }
- }
- else if (headerString == "warp")
- {
- if (opts.OnlyKey)
- continue;
- Console.WriteLine($"File name: {file.Name}");
- Console.WriteLine("ARC type: WARP");
- using (var br = new BinaryReader(stream))
- Console.WriteLine(
- $"Needs a decryption key from the following ARC: {WarpArc.GetKeyWarpName(br)}");
- }
- else
- return Errors.Error(Errors.ErrorCodes.UnknownArcType, file.Name);
- }
- }
- return 0;
- }
- static int Extract(ExtractOptions opts)
- {
- Dictionary<string, byte[]> keys = null;
- Directory.CreateDirectory(opts.Output);
- foreach (var file in Util.EnumerateFiles(opts.Input))
- {
- if (!file.Exists)
- return Errors.Error(Errors.ErrorCodes.NotAFile, file.Name);
- using (var stream = File.OpenRead(file.FullName))
- {
- var header = new byte[4];
- stream.Read(header, 0, header.Length);
- var headerString = Encoding.ASCII.GetString(header);
- stream.Position = 0;
- WarcArc arc = null;
- if (headerString == "warp")
- {
- if (keys == null)
- {
- var err = GetDecryptionKeys(opts, out keys);
- if (err != 0)
- return err;
- arc = new WarpArc(stream, requestedFile =>
- {
- if (keys.TryGetValue("*", out var key))
- return key;
- if (keys.TryGetValue(requestedFile, out key))
- return key;
- if (Directory.Exists(opts.ArcDirectory) &&
- File.Exists(Path.Combine(opts.ArcDirectory, requestedFile)))
- return ReadKeyFromFile(Path.Combine(opts.ArcDirectory, requestedFile));
- if (File.Exists(opts.WarcFile))
- return ReadKeyFromFile(opts.WarcFile);
- throw new FileNotFoundException("No key found for the requested ARC", requestedFile);
- });
- }
- }
- else if (headerString == "warc")
- arc = new WarcArc(stream);
- if (arc == null)
- return Errors.Error(Errors.ErrorCodes.UnknownArcType, file.Name);
- Console.WriteLine($"Extracting {arc.Name}:");
- var arcDir = Path.Combine(opts.Output, Path.GetFileNameWithoutExtension(arc.Name));
- Directory.CreateDirectory(arcDir);
- foreach (var arcEntry in arc.Entries)
- ExtractArchive(arcEntry, arcDir);
- arc.Dispose();
- }
- }
- return 0;
- }
- static void ExtractArchive(ArcEntry entry, string path)
- {
- switch (entry)
- {
- case ArcFileEntry fe:
- var fullName = Path.Combine(path, fe.Name);
- Console.WriteLine(fullName);
- using (var file = File.Create(fullName))
- using (var data = fe.GetDataStream())
- data.CopyTo(file);
- break;
- case ArcDirectoryEntry de:
- var subfolderPath = Path.Combine(path, de.Name);
- Directory.CreateDirectory(subfolderPath);
- foreach (var arcEntry in de.Children)
- ExtractArchive(arcEntry, subfolderPath);
- break;
- }
- }
- static int GetDecryptionKeys(IDecryptionOptions opts, out Dictionary<string, byte[]> keys)
- {
- keys = new Dictionary<string, byte[]>(StringComparer.InvariantCultureIgnoreCase);
- if (!string.IsNullOrWhiteSpace(opts.KeyFile))
- {
- if (!File.Exists(opts.KeyFile))
- return Errors.Error(Errors.ErrorCodes.KeyNotFound);
- foreach (var line in File.ReadAllLines(opts.KeyFile))
- {
- var parts = line.Split(':');
- if (parts.Length != 2)
- return Errors.Error(Errors.ErrorCodes.InvalidKeyFile);
- keys[parts[0].Trim()] = Convert.FromBase64String(parts[1].Trim());
- }
- }
- else if (!string.IsNullOrWhiteSpace(opts.DecryptionKey))
- keys["*"] = Convert.FromBase64String(opts.DecryptionKey);
- else if (string.IsNullOrWhiteSpace(opts.ArcDirectory) && string.IsNullOrWhiteSpace(opts.WarcFile))
- return Errors.Error(Errors.ErrorCodes.KeyNotFound);
- return 0;
- }
- static int Main(string[] args)
- {
- return Parser.Default.ParseArguments<InfoOptions, ExtractOptions, DecryptOptions>(args).MapResult(
- (InfoOptions opts) => DisplayInfo(opts), (ExtractOptions opts) => Extract(opts),
- (DecryptOptions opts) => Decrypt(opts), errs => 1);
- }
- static byte[] ReadKeyFromFile(string filename)
- {
- using (var br = new BinaryReader(File.OpenRead(filename)))
- return br.ReadBytes(2048);
- }
- }
- }
|