Esempio n. 1
0
        static private string GetProgramForBinaryFile(string file)
        {
            using (var input = File.OpenRead(file))
            {
                var guess = new byte[32];

                var    read      = input.Read(guess, 0, (int)Math.Min(guess.Length, input.Length));
                string extension = FileDetection.Detect(guess, read);

                if (_ExtensionLookup.ContainsKey(extension))
                {
                    return(_ExtensionLookup[extension]);
                }
            }
            return(null);
        }
        public static void Main(string[] args)
        {
            bool   showHelp        = false;
            bool   extractUnknowns = true;
            string filterPattern   = null;
            bool   overwriteFiles  = false;
            bool   verbose         = false;
            string currentProject  = null;

            var options = new OptionSet()
            {
                { "o|overwrite", "overwrite existing files", v => overwriteFiles = v != null },
                { "nu|no-unknowns", "don't extract unknown files", v => extractUnknowns = v == null },
                { "f|filter=", "only extract files using pattern", v => filterPattern = v },
                { "v|verbose", "be verbose", v => verbose = v != null },
                { "h|help", "show this message and exit", v => showHelp = v != null },
                { "p|project=", "override current project", v => currentProject = v },
            };

            List <string> extras;

            try
            {
                extras = options.Parse(args);
            }
            catch (OptionException e)
            {
                Console.Write("{0}: ", GetExecutableName());
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `{0} --help' for more information.", GetExecutableName());
                return;
            }

            if (extras.Count < 1 || extras.Count > 2 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ input_tab [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            string tabPath    = Path.GetFullPath(extras[0]);
            string outputPath = extras.Count > 1
                                    ? Path.GetFullPath(extras[1])
                                    : Path.ChangeExtension(tabPath, null) + "_unpack";

            Regex filter = null;

            if (string.IsNullOrEmpty(filterPattern) == false)
            {
                filter = new Regex(filterPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            }

            var manager = ProjectData.Manager.Load(currentProject);

            if (manager.ActiveProject == null)
            {
                Console.WriteLine("Warning: no active project loaded.");
            }

            var hashes = manager.LoadFileLists(null);

            var tab = new ArchiveTableFile();

            using (var input = File.OpenRead(tabPath))
            {
                tab.Deserialize(input);
            }

            var arcPath = Path.ChangeExtension(tabPath, ".arc");

            using (var input = File.OpenRead(arcPath))
            {
                long current = 0;
                long total   = tab.Entries.Count;
                var  padding = total.ToString(CultureInfo.InvariantCulture).Length;

                foreach (var entry in tab.Entries)
                {
                    current++;

                    string name = hashes[entry.NameHash];
                    if (name == null)
                    {
                        if (extractUnknowns == false)
                        {
                            continue;
                        }

                        var guess = new byte[32];

                        input.Seek(entry.Offset, SeekOrigin.Begin);
                        var read = input.Read(guess, 0, (int)Math.Min(guess.Length, entry.Size));

                        var extension = FileDetection.Detect(guess, read);
                        name = entry.NameHash.ToString("X8");
                        name = Path.ChangeExtension(name, "." + extension);
                        name = Path.Combine("__UNKNOWN", extension, name);
                    }
                    else
                    {
                        if (name.StartsWith("/") == true)
                        {
                            name = name.Substring(1);
                        }
                        name = name.Replace('/', Path.DirectorySeparatorChar);
                    }

                    if (filter != null && filter.IsMatch(name) == false)
                    {
                        continue;
                    }

                    var entryPath = Path.Combine(outputPath, name);
                    if (overwriteFiles == false && File.Exists(entryPath) == true)
                    {
                        continue;
                    }

                    if (verbose == true)
                    {
                        Console.WriteLine(
                            "[{0}/{1}] {2}",
                            current.ToString(CultureInfo.InvariantCulture).PadLeft(padding),
                            total,
                            name);
                    }

                    var entryDirectory = Path.GetDirectoryName(entryPath);
                    if (entryDirectory != null)
                    {
                        Directory.CreateDirectory(entryDirectory);
                    }

                    using (var output = File.Create(entryPath))
                    {
                        input.Seek(entry.Offset, SeekOrigin.Begin);
                        output.WriteFromStream(input, entry.Size);
                    }
                }
            }
        }
        public static void Main(string[] args)
        {
            bool verbose  = false;
            bool showHelp = false;

            var options = new OptionSet()
            {
                { "v|verbose", "be verbose (list files)", v => verbose = v != null },
                { "h|help", "show this message and exit", v => showHelp = v != null },
            };

            List <string> extra;

            try
            {
                extra = options.Parse(args);
            }
            catch (OptionException e)
            {
                Console.Write("{0}: ", GetExecutableName());
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `{0} --help' for more information.", GetExecutableName());
                return;
            }

            if (extra.Count < 1 || extra.Count > 2 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ input_pack [output_directory]", GetExecutableName());
                Console.WriteLine("Unpack specified archive.");
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            string inputPath      = extra[0];
            string outputBasePath = extra.Count > 1 ? extra[1] : Path.ChangeExtension(inputPath, null) + "_unpacked";

            Directory.CreateDirectory(outputBasePath);

            using (var input = File.OpenRead(inputPath))
            {
                var header = new PackFile();
                header.Deserialize(input);

                var entryCount = header.Entries.Count;
                for (int i = 0; i < entryCount; i++)
                {
                    var outputPath = Path.Combine(outputBasePath, $"{i}");

                    uint entryOffset     = header.Entries[i].Offset;
                    uint nextEntryOffset = i + 1 >= entryCount ? header.TotalSize : header.Entries[i + 1].Offset;
                    uint entrySize       = nextEntryOffset - entryOffset;

                    input.Position = entryOffset;
                    var extension = FileDetection.Guess(input, (int)entrySize, entrySize);
                    outputPath = Path.ChangeExtension(outputPath, extension);

                    if (verbose == true)
                    {
                        Console.WriteLine(outputPath);
                    }

                    input.Position = entryOffset;
                    using (var output = File.Create(outputPath))
                    {
                        output.WriteFromStream(input, entrySize);
                    }
                }
            }
        }
Esempio n. 4
0
        private static bool GetEntryName(Stream input,
                                         BigFile fat,
                                         Big.Entry entry,
                                         ProjectData.HashList <uint> hashes,
                                         bool extractUnknowns,
                                         bool onlyUnknowns,
                                         out string entryName)
        {
            entryName = hashes[entry.NameHash];

            if (entryName == null)
            {
                if (extractUnknowns == false)
                {
                    return(false);
                }

                string type;
                string extension;
                {
                    var guess = new byte[64];
                    int read  = 0;

                    if (entry.CompressionScheme == Big.CompressionScheme.None)
                    {
                        if (entry.CompressedSize > 0)
                        {
                            input.Seek(entry.Offset, SeekOrigin.Begin);
                            read = input.Read(guess, 0, (int)Math.Min(entry.CompressedSize, guess.Length));
                        }
                    }
                    else
                    {
                        using (var temp = new MemoryStream())
                        {
                            EntryDecompression.Decompress(entry, input, temp);
                            temp.Position = 0;
                            read          = temp.Read(guess, 0, (int)Math.Min(temp.Length, guess.Length));
                        }
                    }

                    var tuple = FileDetection.Detect(guess, Math.Min(guess.Length, read));
                    type      = tuple != null ? tuple.Item1 : "unknown";
                    extension = tuple != null ? tuple.Item2 : null;
                }

                entryName = entry.NameHash.ToString("X8");

                if (string.IsNullOrEmpty(extension) == false)
                {
                    entryName = Path.ChangeExtension(entryName, "." + extension);
                }

                if (string.IsNullOrEmpty(type) == false)
                {
                    entryName = Path.Combine(type, entryName);
                }

                entryName = Path.Combine("__UNKNOWN", entryName);
            }
            else
            {
                if (onlyUnknowns == true)
                {
                    return(false);
                }

                entryName = FilterEntryName(entryName);
            }

            return(true);
        }
        public static void Main(string[] args)
        {
            bool unpackNestedPacks = true;
            bool verbose           = false;
            bool showHelp          = false;

            var options = new OptionSet()
            {
                { "d|dont-unpack-nested-packs", "don't unpack nested .pack files", v => unpackNestedPacks = v == null },
                { "v|verbose", "be verbose", v => verbose = v != null },
                { "h|help", "show this message and exit", v => showHelp = v != null },
            };

            List <string> extra;

            try
            {
                extra = options.Parse(args);
            }
            catch (OptionException e)
            {
                Console.Write("{0}: ", GetExecutableName());
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `{0} --help' for more information.", GetExecutableName());
                return;
            }

            if (extra.Count < 1 || extra.Count > 2 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ input_FILETABLE [output_directory]", GetExecutableName());
                Console.WriteLine("Unpack specified archive.");
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            string inputPath      = extra[0];
            string outputBasePath = extra.Count > 1 ? extra[1] : Path.ChangeExtension(inputPath, null) + "_unpacked";

            FileTableFile table;

            using (var input = File.OpenRead(inputPath))
            {
                table = new FileTableFile();
                table.Deserialize(input);
            }

            var inputBasePath = Path.GetDirectoryName(inputPath);

            // TODO(gibbed):
            //  - generate file index for successful repacking
            //  - better name lookup for name hashes (FNV32)
            //    (don't hardcode the list)
            var names = new string[]
            {
                "MENU_COMMON_PACK",
                "MENU_TEXTURE_MISC_PACK",
                "MN_AT_ORGANIZE",
                "MN_BIRTHDAY",
                "MN_BT_MAIN",
                "MN_BT_RESULT",
                "MN_COMMON",
                "MN_COMMONWIN",
                "MN_EVENT",
                "MN_INPUT",
                "MN_ITEMICON",
                "MN_KEY_LAYOUT",
                "MN_MOVIE",
                "MN_NETWORK",
                "MN_OPTION",
                "MN_ORGANIZE",
                "MN_SHOP2",
                "MN_STAFFROLL",
                "MN_STATUS",
                "MN_TITLE",
                "MN_WARRENREPORT",
                "MN_WORLD",
            };
            var nameHashLookup = names.ToDictionary(v => v.HashFNV32(), v => v);

            var tableManifestPath = Path.Combine(outputBasePath, "@manifest.json");
            var tableManifest     = new FileTableManifest()
            {
                Endian               = table.Endian,
                TitleId1             = table.TitleId1,
                TitleId2             = table.TitleId2,
                Unknown32            = table.Unknown32,
                ParentalLevel        = table.ParentalLevel,
                InstallDataCryptoKey = table.InstallDataCryptoKey,
            };

            foreach (var directory in table.Directories)
            {
                var tableDirectory = new TableDirectory()
                {
                    Id       = directory.Id,
                    BasePath = Path.Combine(outputBasePath, $"{directory.Id}"),
                };

                var fileContainers = new List <IFileContainer>()
                {
                    tableDirectory,
                };

                var binPath = Path.Combine(inputBasePath, $"{directory.Id:X4}.BIN");
                using (var input = File.OpenRead(binPath))
                {
                    var fileQueue = new Queue <QueuedFile>();
                    foreach (var file in directory.Files)
                    {
                        long dataOffset;
                        dataOffset  = directory.DataBaseOffset;
                        dataOffset += (file.DataBlockOffset << directory.DataBlockSize) * FileTableFile.BaseDataBlockSize;

                        fileQueue.Enqueue(new QueuedFile()
                        {
                            Id         = file.Id,
                            Parent     = tableDirectory,
                            NameHash   = file.NameHash,
                            DataOffset = dataOffset,
                            DataSize   = file.DataSize,
                        });
                    }

                    while (fileQueue.Count > 0)
                    {
                        var file   = fileQueue.Dequeue();
                        var parent = file.Parent;

                        var nameBuilder = new StringBuilder();
                        nameBuilder.Append($"{file.Id}");

                        string name = null;
                        if (file.NameHash != null)
                        {
                            if (nameHashLookup.TryGetValue(file.NameHash.Value, out name) == true)
                            {
                                nameBuilder.Append($"_{name}");
                            }
                            else
                            {
                                nameBuilder.Append($"_HASH[{file.NameHash.Value:X8}]");
                            }
                        }

                        if (parent.IdCounts != null)
                        {
                            var idCounts = parent.IdCounts;
                            int idCount;
                            idCounts.TryGetValue(file.Id, out idCount);
                            idCount++;
                            idCounts[file.Id] = idCount;

                            if (idCount > 1)
                            {
                                nameBuilder.Append($"_DUP_{idCount}");
                            }
                        }

                        if (unpackNestedPacks == true && file.DataSize >= 8)
                        {
                            input.Position = file.DataOffset;
                            var fileMagic = input.ReadValueU32(Endian.Little);
                            if (fileMagic == PackFile.Signature || fileMagic.Swap() == PackFile.Signature)
                            {
                                input.Position = file.DataOffset;
                                var nestedPack = HandleNestedPack(input, fileQueue, file.Id, nameBuilder.ToString(), parent);
                                fileContainers.Add(nestedPack);
                                parent.FileManifests.Add(new FileTableManifest.File()
                                {
                                    Id       = file.Id,
                                    NameHash = file.NameHash,
                                    Name     = name,
                                    IsPack   = true,
                                    PackId   = PackId.Create(file.PackRawId),
                                    Path     = CleanPathForManifest(PathHelper.GetRelativePath(parent.BasePath, nestedPack.ManifestPath)),
                                });
                                continue;
                            }
                        }

                        var outputPath = Path.Combine(parent.BasePath, nameBuilder.ToString());

                        var outputParentPath = Path.GetDirectoryName(outputPath);
                        if (string.IsNullOrEmpty(outputParentPath) == false)
                        {
                            Directory.CreateDirectory(outputParentPath);
                        }

                        input.Position = file.DataOffset;
                        var extension = FileDetection.Guess(input, (int)file.DataSize, file.DataSize);
                        outputPath = Path.ChangeExtension(outputPath, extension);

                        if (verbose == true)
                        {
                            Console.WriteLine(outputPath);
                        }

                        input.Position = file.DataOffset;
                        using (var output = File.Create(outputPath))
                        {
                            output.WriteFromStream(input, file.DataSize);
                        }

                        parent.FileManifests.Add(new FileTableManifest.File()
                        {
                            Id       = file.Id,
                            NameHash = file.NameHash,
                            Name     = name,
                            PackId   = PackId.Create(file.PackRawId),
                            Path     = CleanPathForManifest(PathHelper.GetRelativePath(parent.BasePath, outputPath)),
                        });
                    }
                }

                foreach (var fileContainer in fileContainers)
                {
                    WriteManifest(fileContainer.ManifestPath, fileContainer);
                }

                tableManifest.Directories.Add(new FileTableManifest.Directory()
                {
                    Id              = directory.Id,
                    DataBlockSize   = directory.DataBlockSize,
                    IsInInstallData = directory.IsInInstallData,
                    FileManifest    = CleanPathForManifest(
                        PathHelper.GetRelativePath(outputBasePath, tableDirectory.ManifestPath)),
                });
            }

            WriteManifest(tableManifestPath, tableManifest);
        }
Esempio n. 6
0
        public static void Main(string[] args)
        {
            bool   showHelp        = false;
            bool   extractUnknowns = true;
            string filterPattern   = null;
            bool   overwriteFiles  = false;
            bool   paranoia        = false;
            bool   verbose         = false;

            var options = new OptionSet()
            {
                { "o|overwrite", "overwrite existing files", v => overwriteFiles = v != null },
                { "nu|no-unknowns", "don't extract unknown files", v => extractUnknowns = v == null },
                { "f|filter=", "only extract files using pattern", v => filterPattern = v },
                { "p|paranoid", "be paranoid (validate hash when uncompressing files)", v => paranoia = v != null },
                { "v|verbose", "be verbose", v => verbose = v != null },
                { "h|help", "show this message and exit", v => showHelp = v != null },
            };

            List <string> extras;

            try
            {
                extras = options.Parse(args);
            }
            catch (OptionException e)
            {
                Console.Write("{0}: ", GetExecutableName());
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `{0} --help' for more information.", GetExecutableName());
                return;
            }

            if (extras.Count < 1 || extras.Count > 2 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ input_bix [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            string bixPath    = Path.GetFullPath(extras[0]);
            string outputPath = extras.Count > 1
                                    ? Path.GetFullPath(extras[1])
                                    : Path.ChangeExtension(bixPath, null) + "_unpack";

            Regex filter = null;

            if (string.IsNullOrEmpty(filterPattern) == false)
            {
                filter = new Regex(filterPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            }

            var manager = ProjectData.Manager.Load();

            if (manager.ActiveProject == null)
            {
                Console.WriteLine("Warning: no active project loaded.");
            }

            var hashes = manager.LoadListsBigNames();

            var bix = new BigFileInventory();

            using (var input = File.OpenRead(bixPath))
            {
                bix.Deserialize(input, Endian.Little);
            }

            var basePath = Path.GetDirectoryName(bixPath);

            if (string.IsNullOrEmpty(basePath) == true)
            {
                throw new InvalidOperationException();
            }

            var bigPath = Path.Combine(basePath, bix.BigFileName + ".big");

            using (var input = File.OpenRead(bigPath))
            {
                long current = 0;
                long total   = bix.Entries.Count;

                foreach (var entry in bix.Entries)
                {
                    current++;

                    string name = hashes[entry.Id];
                    if (name == null)
                    {
                        if (extractUnknowns == false)
                        {
                            continue;
                        }

                        KeyValuePair <string, string> extension;

                        // detect type
                        {
                            var guess = new byte[64];
                            int read  = 0;

                            var offset = (entry.Offset << 2) + (entry.Size.LoadOffset & 0xFFF);
                            if (entry.Size.CompressedSize == 0)
                            {
                                if (entry.Size.UncompressedSize > 0)
                                {
                                    input.Seek(offset, SeekOrigin.Begin);
                                    read = input.Read(guess, 0, (int)Math.Min(entry.Size.UncompressedSize, guess.Length));
                                }
                            }
                            else
                            {
                                input.Seek(offset, SeekOrigin.Begin);

                                // todo: don't uncompress everything
                                var uncompressedData = QuickCompression.Decompress(input);
                                read = Math.Min(guess.Length, uncompressedData.Length);
                                Array.Copy(uncompressedData, 0, guess, 0, read);
                            }

                            extension = FileDetection.Detect(guess, Math.Min(guess.Length, read));
                        }

                        name = entry.Id.ToString("X8");
                        name = Path.ChangeExtension(name, "." + extension.Value);
                        name = Path.Combine(extension.Key, name);
                        name = Path.Combine("__UNKNOWN", name);
                    }
                    else
                    {
                        name = name.Replace(@"/", @"\");
                        if (name.StartsWith(@"\") == true)
                        {
                            name = name.Substring(1);
                        }
                    }

                    if (filter != null && filter.IsMatch(name) == false)
                    {
                        continue;
                    }

                    var entryPath = Path.Combine(outputPath, name);
                    if (overwriteFiles == false && File.Exists(entryPath) == true)
                    {
                        continue;
                    }

                    var entryParentPath = Path.GetDirectoryName(entryPath);
                    if (string.IsNullOrEmpty(entryParentPath) == true)
                    {
                        throw new InvalidOperationException();
                    }
                    Directory.CreateDirectory(entryParentPath);

                    if (verbose == true)
                    {
                        Console.WriteLine("[{0}/{1}] {2}", current, total, name);
                    }

                    using (var output = File.Create(entryPath))
                    {
                        if (entry.Size.CompressedSize == 0)
                        {
                            if (entry.Size.LoadOffset != 0 ||
                                entry.Size.CompressedExtra != 0)
                            {
                                throw new InvalidOperationException();
                            }

                            if (entry.Size.UncompressedSize > 0)
                            {
                                input.Seek(entry.Offset << 2, SeekOrigin.Begin);
                                output.WriteFromStream(input, entry.Size.UncompressedSize);
                            }
                        }
                        else
                        {
                            var uncompressedSize = entry.Size.CompressedSize +
                                                   entry.Size.LoadOffset -
                                                   entry.Size.CompressedExtra;
                            if (uncompressedSize != entry.Size.UncompressedSize)
                            {
                            }

                            if (entry.Size.UncompressedSize > 0)
                            {
                                input.Seek((entry.Offset << 2) + (entry.Size.LoadOffset & 0xFFF), SeekOrigin.Begin);
                                QuickCompression.Decompress(input, output);
                            }
                        }
                    }
                }
            }
        }
Esempio n. 7
0
        public static void Main(string[] args)
        {
            bool verbose  = false;
            bool showHelp = false;

            var options = new OptionSet()
            {
                { "v|verbose", "be verbose", v => verbose = v != null },
                { "h|help", "show this message and exit", v => showHelp = v != null },
            };

            List <string> extra;

            try
            {
                extra = options.Parse(args);
            }
            catch (OptionException e)
            {
                Console.Write("{0}: ", GetExecutableName());
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `{0} --help' for more information.", GetExecutableName());
                return;
            }

            if (extra.Count < 1 || extra.Count > 2 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ input_blob [output_directory]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            string inputPath      = extra[0];
            string baseOutputPath = extra.Count > 1 ? extra[1] : Path.ChangeExtension(inputPath, null) + "_unpacked";

            using (var input = File.OpenRead(inputPath))
            {
                const Endian endian = Endian.Little;

                var count = input.ReadValueU32(endian);

                var ids   = new uint[count];
                var sizes = new uint[count];

                for (uint i = 0; i < count; i++)
                {
                    ids[i]   = input.ReadValueU32(endian);
                    sizes[i] = input.ReadValueU32(endian);
                }
                var end = (uint)input.Length;

                for (uint i = 0; i < count; i++)
                {
                    uint id   = ids[i];
                    uint size = sizes[i];

                    long currentPosition = input.Position;
                    var  extension       = FileDetection.Guess(input, (int)size, size);
                    input.Position = currentPosition;

                    var name = string.Format(
                        "{0}_{1:X4}_{2:X2}_{3:X2}",
                        i,
                        (id & 0x0000FFFF) >> 0,
                        (id & 0x00FF0000) >> 16,
                        (id & 0xFF000000) >> 24);

                    var outputPath = Path.Combine(baseOutputPath, Path.ChangeExtension(name, extension));

                    if (verbose == true)
                    {
                        Console.WriteLine(outputPath);
                    }

                    var outputParentPath = Path.GetDirectoryName(outputPath);
                    if (string.IsNullOrEmpty(outputParentPath) == false)
                    {
                        Directory.CreateDirectory(outputParentPath);
                    }

                    using (var output = File.Create(outputPath))
                    {
                        output.WriteFromStream(input, size);
                    }
                }
            }
        }
Esempio n. 8
0
        public static void Main(string[] args)
        {
            bool   showHelp        = false;
            bool   extractUnknowns = true;
            string filterPattern   = null;
            bool   overwriteFiles  = false;
            bool   verbose         = false;
            string currentProject  = null;

            var options = new OptionSet()
            {
                { "o|overwrite", "overwrite existing files", v => overwriteFiles = v != null },
                { "nu|no-unknowns", "don't extract unknown files", v => extractUnknowns = v == null },
                { "f|filter=", "only extract files using pattern", v => filterPattern = v },
                { "v|verbose", "be verbose", v => verbose = v != null },
                { "h|help", "show this message and exit", v => showHelp = v != null },
                { "p|project=", "override current project", v => currentProject = v },
            };

            List <string> extras;

            try
            {
                extras = options.Parse(args);
            }
            catch (OptionException e)
            {
                Console.Write("{0}: ", GetExecutableName());
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `{0} --help' for more information.", GetExecutableName());
                return;
            }

            if (extras.Count < 1 || extras.Count > 2 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ input_tab [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            string tabPath    = Path.GetFullPath(extras[0]);
            string outputPath = extras.Count > 1
                                    ? Path.GetFullPath(extras[1])
                                    : Path.ChangeExtension(tabPath, null) + "_unpack";

            Regex filter = null;

            if (string.IsNullOrEmpty(filterPattern) == false)
            {
                filter = new Regex(filterPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            }

            var manager = ProjectData.Manager.Load(currentProject);

            if (manager.ActiveProject == null)
            {
                Console.WriteLine("Warning: no active project loaded.");
            }

            var hashes = manager.LoadFileLists();

            var tab = new ArchiveTableFile();

            using (var input = File.OpenRead(tabPath))
            {
                tab.Deserialize(input);
            }

            var arcPath = Path.ChangeExtension(tabPath, ".arc");

            var compressedBlockBytes   = new byte[tab.MaxCompressedBlockSize];
            var uncompressedBlockBytes = new byte[tab.UncompressedBlockSize];

            var singleCompressedBlock = new List <ArchiveTableFile.CompressedBlockInfo>();

            singleCompressedBlock.Add(new ArchiveTableFile.CompressedBlockInfo());

            using (var input = File.OpenRead(arcPath))
            {
                long current = 0;
                long total   = tab.Entries.Count;
                var  padding = total.ToString(CultureInfo.InvariantCulture).Length;

                foreach (var entry in tab.Entries)
                {
                    current++;

                    string name = hashes[entry.NameHash];
                    if (name == null)
                    {
                        if (extractUnknowns == false)
                        {
                            continue;
                        }

                        var guess = new byte[32];

                        input.Position = entry.Offset;

                        int read;
                        if (entry.CompressionType == CompressionType.None ||
                            entry.CompressedSize == entry.UncompressedSize)
                        {
                            read = input.Read(guess, 0, (int)Math.Min(guess.Length, entry.CompressedSize));
                        }
                        else
                        {
                            var decompress = GetDecompress(entry.CompressionType);

                            ArchiveTableFile.CompressedBlockInfo compressedBlock;
                            if (entry.CompressedBlockIndex == 0)
                            {
                                compressedBlock = new ArchiveTableFile.CompressedBlockInfo(
                                    entry.CompressedSize,
                                    entry.UncompressedSize);
                            }
                            else
                            {
                                compressedBlock = tab.CompressedBlocks[entry.CompressedBlockIndex];
                            }

                            read = input.Read(compressedBlockBytes, 0, (int)compressedBlock.CompressedSize);
                            if (read != compressedBlock.CompressedSize)
                            {
                                throw new EndOfStreamException();
                            }

                            read = decompress(
                                compressedBlockBytes,
                                0,
                                (int)compressedBlock.CompressedSize,
                                uncompressedBlockBytes,
                                0,
                                (int)compressedBlock.UncompressedSize);
                            if (read != compressedBlock.UncompressedSize)
                            {
                                throw new InvalidOperationException();
                            }

                            var guessSize = Math.Min(32, (int)compressedBlock.UncompressedSize);
                            Array.Copy(uncompressedBlockBytes, 0, guess, 0, guessSize);
                        }

                        var extension = FileDetection.Detect(guess, read);
                        name = entry.NameHash.ToString("X8");
                        name = Path.ChangeExtension(name, "." + extension);
                        name = Path.Combine("__UNKNOWN", extension, name);
                    }
                    else
                    {
                        if (name.StartsWith("/") == true)
                        {
                            name = name.Substring(1);
                        }
                        name = name.Replace('/', Path.DirectorySeparatorChar);
                    }

                    if (filter != null && filter.IsMatch(name) == false)
                    {
                        continue;
                    }

                    var entryPath = Path.Combine(outputPath, name);
                    if (overwriteFiles == false && File.Exists(entryPath) == true)
                    {
                        continue;
                    }

                    if (verbose == true)
                    {
                        Console.WriteLine(
                            "[{0}/{1}] {2}",
                            current.ToString(CultureInfo.InvariantCulture).PadLeft(padding),
                            total,
                            name);
                    }

                    var entryDirectory = Path.GetDirectoryName(entryPath);
                    if (entryDirectory != null)
                    {
                        Directory.CreateDirectory(entryDirectory);
                    }

                    input.Position = entry.Offset;
                    using (var output = File.Create(entryPath))
                    {
                        if (entry.CompressionType == CompressionType.None ||
                            entry.CompressedSize == entry.UncompressedSize)
                        {
                            if (entry.CompressedSize != entry.UncompressedSize)
                            {
                                throw new InvalidOperationException();
                            }

                            output.WriteFromStream(input, entry.CompressedSize);
                        }
                        else
                        {
                            var decompress = GetDecompress(entry.CompressionType);

                            List <ArchiveTableFile.CompressedBlockInfo> compressedBlocks;
                            ushort compressedBlockCount;
                            ushort compressedBlockIndex = entry.CompressedBlockIndex;
                            if (compressedBlockIndex == 0)
                            {
                                compressedBlocks    = singleCompressedBlock;
                                compressedBlocks[0] = new ArchiveTableFile.CompressedBlockInfo(
                                    entry.CompressedSize,
                                    entry.UncompressedSize);
                                compressedBlockCount = 1;
                            }
                            else
                            {
                                compressedBlocks = tab.CompressedBlocks;
                                long uncompressedSize = 0;
                                compressedBlockCount = 0;
                                for (int i = compressedBlockIndex; uncompressedSize < entry.UncompressedSize; i++)
                                {
                                    uncompressedSize += compressedBlocks[i].UncompressedSize;
                                    compressedBlockCount++;
                                }
                            }

                            long remaining = entry.UncompressedSize;
                            for (int i = 0; i < compressedBlockCount; i++)
                            {
                                var compressedBlock = compressedBlocks[compressedBlockIndex];

                                var read = input.Read(compressedBlockBytes, 0, (int)compressedBlock.CompressedSize);
                                if (read != compressedBlock.CompressedSize)
                                {
                                    throw new EndOfStreamException();
                                }

                                var result = decompress(
                                    compressedBlockBytes,
                                    0,
                                    (int)compressedBlock.CompressedSize,
                                    uncompressedBlockBytes,
                                    0,
                                    (int)compressedBlock.UncompressedSize);
                                if (result != compressedBlock.UncompressedSize)
                                {
                                    throw new InvalidOperationException();
                                }
                                output.Write(uncompressedBlockBytes, 0, (int)compressedBlock.UncompressedSize);

                                remaining -= compressedBlock.UncompressedSize;
                                compressedBlockIndex++;
                            }

                            if (remaining != 0)
                            {
                                throw new InvalidOperationException();
                            }
                        }
                    }
                }
            }
        }