Browse Source

Add option to extract archives

ghorsington 5 years ago
parent
commit
03270a636b
2 changed files with 183 additions and 108 deletions
  1. 183 108
      ArcToolkitCLI/Program.cs
  2. BIN
      lib/COM3D2.Toolkit.dll

+ 183 - 108
ArcToolkitCLI/Program.cs

@@ -6,27 +6,36 @@ using System.Linq;
 using System.Text;
 using CommandLine;
 using COM3D2.Toolkit.Arc;
+using COM3D2.Toolkit.Arc.Files;
 using Ganss.IO;
 
 namespace ArcToolkitCLI
 {
-    internal interface IOptions
+    interface IOptions
     {
         [Value(0, MetaName = "input", HelpText = "Input ARC files")]
         IEnumerable<string> Input { get; set; }
     }
 
-    internal interface IDecryptionOptions
+    interface IOutputOptions
     {
-        [Option("arc-search-dir", Required = false, HelpText = "Directory where to search ARC file to decrypt the WARP file")]
+        [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)]
+            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")]
@@ -34,44 +43,40 @@ namespace ArcToolkitCLI
     }
 
     [Verb("extract", HelpText = "Extract the contents of the given ARC files")]
-    internal class ExtractOptions : IOptions, IDecryptionOptions
+    class ExtractOptions : IOptions, IDecryptionOptions, IOutputOptions
     {
-        [Option('o', "output", HelpText = "Output directory", Required = true)]
-        public string Output { get; set; }
-
         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")]
-    internal class InfoOptions : IOptions
+    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)]
+            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")]
-    internal class DecryptOptions : IOptions, IDecryptionOptions
+    class DecryptOptions : IOptions, IDecryptionOptions, IOutputOptions
     {
-        [Option('o', "output", HelpText = "Output directory", Default = ".")]
-        public string Output { get; set; }
-
         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; }
     }
 
-    internal static class Errors
+    static class Errors
     {
         public enum ErrorCodes
         {
@@ -84,11 +89,11 @@ namespace ArcToolkitCLI
 
         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}"
+            "{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)
@@ -98,13 +103,13 @@ namespace ArcToolkitCLI
         }
     }
 
-    internal static class Util
+    static class Util
     {
-        private static readonly char[] GlobCharacters = {'*', '{', '}', '[', ']', '?'};
+        static readonly char[] GlobCharacters = {'*', '{', '}', '[', ']', '?'};
 
         public static IEnumerable<FileSystemInfoBase> EnumerateFiles(IEnumerable<string> patterns)
         {
-            foreach (string pattern in patterns)
+            foreach (var pattern in patterns)
                 if (IsGlob(pattern))
                     foreach (var fileSystemInfoBase in Glob.Expand(pattern))
                         yield return fileSystemInfoBase;
@@ -112,26 +117,69 @@ namespace ArcToolkitCLI
                     yield return new FileInfoWrapper(new FileSystem(), new FileInfo(pattern));
         }
 
-        private static bool IsGlob(string path)
-        {
-            return path.IndexOfAny(GlobCharacters) >= 0;
-        }
+        static bool IsGlob(string path) { return path.IndexOfAny(GlobCharacters) >= 0; }
     }
 
-    internal class Program
+    class Program
     {
-        private static int Main(string[] args)
+        static int Decrypt(DecryptOptions opts)
         {
-            return Parser.Default.ParseArguments<InfoOptions, ExtractOptions, DecryptOptions>(args)
-                         .MapResult((InfoOptions opts) => DisplayInfo(opts),
-                                    (ExtractOptions opts) => Extract(opts),
-                                    (DecryptOptions opts) => Decrypt(opts),
-                                    errs => 1);
+            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;
         }
 
-        private static int DisplayInfo(InfoOptions opts)
+        static int DisplayInfo(InfoOptions opts)
         {
-            bool first = true;
+            var first = true;
             foreach (var file in Util.EnumerateFiles(opts.Input))
             {
                 if (!file.Exists)
@@ -145,14 +193,14 @@ namespace ArcToolkitCLI
                 {
                     var header = new byte[4];
                     stream.Read(header, 0, header.Length);
-                    string headerString = Encoding.ASCII.GetString(header);
+                    var headerString = Encoding.ASCII.GetString(header);
                     stream.Position = 0;
 
                     if (headerString == "warc")
                     {
                         var key = new byte[2048];
                         stream.Read(key, 0, key.Length);
-                        string keyBase64 = Convert.ToBase64String(key);
+                        var keyBase64 = Convert.ToBase64String(key);
                         if (opts.OnlyKey)
                         {
                             Console.WriteLine($"{Path.GetFileName(file.FullName)}:{keyBase64}");
@@ -177,108 +225,135 @@ namespace ArcToolkitCLI
                         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)}");
+                            Console.WriteLine(
+                                $"Needs a decryption key from the following ARC: {WarpArc.GetKeyWarpName(br)}");
                     }
                     else
-                    {
                         return Errors.Error(Errors.ErrorCodes.UnknownArcType, file.Name);
-                    }
                 }
             }
 
             return 0;
         }
 
-        private static int Extract(ExtractOptions opts)
+        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;
         }
 
-        private static byte[] ReadKeyFromFile(string filename)
+        static void ExtractArchive(ArcEntry entry, string path)
         {
-            using (var br = new BinaryReader(File.OpenRead(filename)))
-                return br.ReadBytes(2048);
+            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;
+            }
         }
 
-        private static int Decrypt(DecryptOptions opts)
+        static int GetDecryptionKeys(IDecryptionOptions opts, out Dictionary<string, byte[]> keys)
         {
-            var keysDict = new Dictionary<string, byte[]>(StringComparer.InvariantCultureIgnoreCase);
+            keys = new Dictionary<string, byte[]>(StringComparer.InvariantCultureIgnoreCase);
             if (!string.IsNullOrWhiteSpace(opts.KeyFile))
             {
                 if (!File.Exists(opts.KeyFile))
                     return Errors.Error(Errors.ErrorCodes.KeyNotFound);
 
-                foreach (string line in File.ReadAllLines(opts.KeyFile))
+                foreach (var line in File.ReadAllLines(opts.KeyFile))
                 {
                     var parts = line.Split(':');
                     if (parts.Length != 2)
                         return Errors.Error(Errors.ErrorCodes.InvalidKeyFile);
-                    keysDict[parts[0].Trim()] = Convert.FromBase64String(parts[1].Trim());
+                    keys[parts[0].Trim()] = Convert.FromBase64String(parts[1].Trim());
                 }
             }
             else if (!string.IsNullOrWhiteSpace(opts.DecryptionKey))
-            {
-                keysDict["*"] = Convert.FromBase64String(opts.DecryptionKey);
-            }
+                keys["*"] = Convert.FromBase64String(opts.DecryptionKey);
             else if (string.IsNullOrWhiteSpace(opts.ArcDirectory) && string.IsNullOrWhiteSpace(opts.WarcFile))
-            {
                 return Errors.Error(Errors.ErrorCodes.KeyNotFound);
-            }
 
-            Directory.CreateDirectory(opts.Output);
-
-            foreach (var file in Util.EnumerateFiles(opts.Input))
-            {
-                if (!file.Exists)
-                    return Errors.Error(Errors.ErrorCodes.NotAFile, file.Name);
+            return 0;
+        }
 
-                using (var stream = File.OpenRead(file.FullName))
-                {
-                    var header = new byte[4];
-                    stream.Read(header, 0, header.Length);
-                    string 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);
-                                                                });
-
-                            using (var output =
-                                    File.Create(Path.Combine(Path.GetDirectoryName(opts.Output), Path.GetFileNameWithoutExtension(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);
-                    }
-                }
-            }
+        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);
+        }
 
-            return 0;
+        static byte[] ReadKeyFromFile(string filename)
+        {
+            using (var br = new BinaryReader(File.OpenRead(filename)))
+                return br.ReadBytes(2048);
         }
     }
 }

BIN
lib/COM3D2.Toolkit.dll