Bläddra i källkod

Move all commands into separate classes

ghorsington 5 år sedan
förälder
incheckning
b8535d89d6

+ 6 - 5
ArcToolkitCLI/App.config

@@ -1,6 +1,7 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
+
 <configuration>
-    <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
-    </startup>
-</configuration>
+  <startup>
+    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
+  </startup>
+</configuration>

+ 10 - 0
ArcToolkitCLI/ArcToolkitCLI.csproj

@@ -52,8 +52,18 @@
     </Reference>
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Commands\DecryptCommand.cs" />
+    <Compile Include="Commands\ExtractCommand.cs" />
+    <Compile Include="Commands\ICommand.cs" />
+    <Compile Include="Commands\InfoCommand.cs" />
+    <Compile Include="Commands\Options\IInputOptions.cs" />
+    <Compile Include="Commands\Options\IOutputOptions.cs" />
+    <Compile Include="Commands\Options\IDecryptionOptions.cs" />
+    <Compile Include="Util\Errors.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Util\Encryption.cs" />
+    <Compile Include="Util\Glob.cs" />
   </ItemGroup>
   <ItemGroup>
     <None Include="App.config" />

+ 80 - 0
ArcToolkitCLI/Commands/DecryptCommand.cs

@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using ArcToolkitCLI.Commands.Options;
+using ArcToolkitCLI.Util;
+using COM3D2.Toolkit.Arc;
+using CommandLine;
+
+namespace ArcToolkitCLI.Commands
+{
+    [Verb("decrypt", HelpText = "Decrypts the provided WARP files")]
+    public class DecryptCommand : ICommand, IInputOptions, IDecryptionOptions, IOutputOptions
+    {
+        public int Run()
+        {
+            var errCode = Encryption.GetDecryptionKeys(this, out var keysDict);
+            if (errCode != 0)
+                return errCode;
+
+            Directory.CreateDirectory(Output);
+
+            foreach (var file in Glob.EnumerateFiles(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(ArcDirectory) &&
+                                    File.Exists(Path.Combine(ArcDirectory, requestedFile)))
+                                    return Encryption.ReadKeyFromFile(Path.Combine(ArcDirectory, requestedFile));
+                                if (File.Exists(WarcFile))
+                                    return Encryption.ReadKeyFromFile(WarcFile);
+                                throw new FileNotFoundException("No key found for the requested ARC", requestedFile);
+                            });
+
+                            Console.WriteLine(
+                                $"Writing {Path.Combine(Output, file.Name)} with length {arcStream.Length}");
+                            using (var output = File.Create(Path.Combine(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;
+        }
+
+        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; }
+    }
+}

+ 112 - 0
ArcToolkitCLI/Commands/ExtractCommand.cs

@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using ArcToolkitCLI.Commands.Options;
+using ArcToolkitCLI.Util;
+using COM3D2.Toolkit.Arc;
+using COM3D2.Toolkit.Arc.Files;
+using CommandLine;
+
+namespace ArcToolkitCLI.Commands
+{
+    [Verb("extract", HelpText = "Extract the contents of the given ARC files")]
+    public class ExtractCommand : ICommand, IInputOptions, IDecryptionOptions, IOutputOptions
+    {
+        public int Run()
+        {
+            Dictionary<string, byte[]> keys = null;
+
+            Directory.CreateDirectory(Output);
+
+            foreach (var file in Glob.EnumerateFiles(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 = Encryption.GetDecryptionKeys(this, 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(ArcDirectory) &&
+                                    File.Exists(Path.Combine(ArcDirectory, requestedFile)))
+                                    return Encryption.ReadKeyFromFile(Path.Combine(ArcDirectory, requestedFile));
+                                if (File.Exists(WarcFile))
+                                    return Encryption.ReadKeyFromFile(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(Output, Path.GetFileNameWithoutExtension(arc.Name));
+
+                    Directory.CreateDirectory(arcDir);
+                    foreach (var arcEntry in arc.Entries)
+                        ExtractArchive(arcEntry, arcDir);
+
+                    arc.Dispose();
+                }
+            }
+
+            return 0;
+        }
+
+        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; }
+
+        private 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;
+            }
+        }
+    }
+}

+ 7 - 0
ArcToolkitCLI/Commands/ICommand.cs

@@ -0,0 +1,7 @@
+namespace ArcToolkitCLI.Commands
+{
+    public interface ICommand
+    {
+        int Run();
+    }
+}

+ 86 - 0
ArcToolkitCLI/Commands/InfoCommand.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using ArcToolkitCLI.Commands.Options;
+using ArcToolkitCLI.Util;
+using COM3D2.Toolkit.Arc;
+using CommandLine;
+
+namespace ArcToolkitCLI.Commands
+{
+    [Verb("info", HelpText = "Display information about the given ARC files")]
+    public class InfoCommand : IInputOptions, ICommand
+    {
+        [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 int Run()
+        {
+            var first = true;
+            foreach (var file in Glob.EnumerateFiles(Input))
+            {
+                if (!file.Exists)
+                    return Errors.Error(Errors.ErrorCodes.NotAFile, file.Name);
+
+                if (!first && !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 (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 (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;
+        }
+
+        public IEnumerable<string> Input { get; set; }
+    }
+}

+ 23 - 0
ArcToolkitCLI/Commands/Options/IDecryptionOptions.cs

@@ -0,0 +1,23 @@
+using CommandLine;
+
+namespace ArcToolkitCLI.Commands.Options
+{
+    public 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; }
+    }
+}

+ 11 - 0
ArcToolkitCLI/Commands/Options/IInputOptions.cs

@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using CommandLine;
+
+namespace ArcToolkitCLI.Commands.Options
+{
+    internal interface IInputOptions
+    {
+        [Value(0, MetaName = "input", HelpText = "Input ARC files")]
+        IEnumerable<string> Input { get; set; }
+    }
+}

+ 10 - 0
ArcToolkitCLI/Commands/Options/IOutputOptions.cs

@@ -0,0 +1,10 @@
+using CommandLine;
+
+namespace ArcToolkitCLI.Commands.Options
+{
+    internal interface IOutputOptions
+    {
+        [Option('o', "output", HelpText = "Output directory", Default = ".")]
+        string Output { get; set; }
+    }
+}

+ 8 - 350
ArcToolkitCLI/Program.cs

@@ -1,359 +1,17 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.IO.Abstractions;
-using System.Linq;
-using System.Text;
+using System.Linq;
+using ArcToolkitCLI.Commands;
 using CommandLine;
-using COM3D2.Toolkit.Arc;
-using COM3D2.Toolkit.Arc.Files;
-using Ganss.IO;
 
 namespace ArcToolkitCLI
 {
-    interface IOptions
+    internal class Program
     {
-        [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)
+        private static int Main(string[] args)
         {
-            using (var br = new BinaryReader(File.OpenRead(filename)))
-                return br.ReadBytes(2048);
+            var commandTypes = typeof(Program).Assembly.GetTypes()
+                .Where(t => !t.IsInterface && !t.IsAbstract && typeof(ICommand).IsAssignableFrom(t)).ToArray();
+            return Parser.Default.ParseArguments(args, commandTypes)
+                .MapResult((object t) => ((ICommand) t).Run(), errs => 1);
         }
     }
 }

+ 1 - 2
ArcToolkitCLI/Properties/AssemblyInfo.cs

@@ -1,5 +1,4 @@
 using System.Reflection;
-using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
 // General Information about an assembly is controlled through the following
@@ -33,4 +32,4 @@ using System.Runtime.InteropServices;
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
 [assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 45 - 0
ArcToolkitCLI/Util/Encryption.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace ArcToolkitCLI.Util
+{
+    public static class Encryption
+    {
+        public static byte[] ReadKeyFromFile(string filename)
+        {
+            using (var br = new BinaryReader(File.OpenRead(filename)))
+            {
+                return br.ReadBytes(2048);
+            }
+        }
+
+        public 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;
+        }
+    }
+}

+ 31 - 0
ArcToolkitCLI/Util/Errors.cs

@@ -0,0 +1,31 @@
+using System;
+
+namespace ArcToolkitCLI.Util
+{
+    public 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;
+        }
+    }
+}

+ 26 - 0
ArcToolkitCLI/Util/Glob.cs

@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Abstractions;
+
+namespace ArcToolkitCLI.Util
+{
+    internal class Glob
+    {
+        public static readonly char[] GlobCharacters = {'*', '{', '}', '[', ']', '?'};
+
+        public static IEnumerable<FileSystemInfoBase> EnumerateFiles(IEnumerable<string> patterns)
+        {
+            foreach (var pattern in patterns)
+                if (IsGlob(pattern))
+                    foreach (var fileSystemInfoBase in Ganss.IO.Glob.Expand(pattern))
+                        yield return fileSystemInfoBase;
+                else
+                    yield return new FileInfoWrapper(new FileSystem(), new FileInfo(pattern));
+        }
+
+        public static bool IsGlob(string path)
+        {
+            return path.IndexOfAny(GlobCharacters) >= 0;
+        }
+    }
+}

+ 1 - 0
ArcToolkitCLI/packages.config

@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
+
 <packages>
   <package id="Glob.cs" version="3.0.27" targetFramework="net461" />
   <package id="ILRepack" version="2.0.16" targetFramework="net461" />