public static void Main(string[] args)
        {
            bool   showHelp       = false;
            string currentProject = null;

            var options = new OptionSet()
            {
                { "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 != 0 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            Console.WriteLine("Loading project...");

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

            if (manager.ActiveProject == null)
            {
                Console.WriteLine("Nothing to do: no active project loaded.");
                return;
            }

            var project = manager.ActiveProject;
            var hashes  = manager.LoadListsBigNames();

            var installPath = project.InstallPath;
            var listsPath   = project.ListsPath;

            if (installPath == null)
            {
                Console.WriteLine("Could not detect install path.");
                return;
            }
            else if (listsPath == null)
            {
                Console.WriteLine("Could not detect lists path.");
                return;
            }

            Console.WriteLine("Searching for archives...");
            var inputPaths = new List <string>();

            inputPaths.AddRange(Directory.GetFiles(installPath, "*.bix", SearchOption.AllDirectories));

            var outputPaths = new List <string>();

            var breakdown = new Breakdown();

            Console.WriteLine("Processing...");
            foreach (var inputPath in inputPaths)
            {
                var outputPath = GetListPath(installPath, inputPath);
                if (outputPath == null)
                {
                    throw new InvalidOperationException();
                }

                Console.WriteLine(outputPath);
                outputPath = Path.Combine(listsPath, outputPath);

                if (outputPaths.Contains(outputPath) == true)
                {
                    throw new InvalidOperationException();
                }

                outputPaths.Add(outputPath);

                var bix = new BigFileInventory();

                if (File.Exists(inputPath + ".bak") == true)
                {
                    using (var input = File.OpenRead(inputPath + ".bak"))
                    {
                        bix.Deserialize(input, Endian.Little);
                    }
                }
                else
                {
                    using (var input = File.OpenRead(inputPath))
                    {
                        bix.Deserialize(input, Endian.Little);
                    }
                }

                var localBreakdown = new Breakdown();

                var names = new List <string>();
                foreach (var nameHash in bix.Entries.Select(e => e.Id).Distinct())
                {
                    var name = hashes[nameHash];
                    if (name != null)
                    {
                        if (names.Contains(name) == false)
                        {
                            names.Add(name);
                            localBreakdown.Known++;
                        }
                    }

                    localBreakdown.Total++;
                }

                breakdown.Known += localBreakdown.Known;
                breakdown.Total += localBreakdown.Total;

                names.Sort();

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

                using (var output = new StreamWriter(outputPath))
                {
                    output.WriteLine("; {0}", localBreakdown);

                    foreach (string name in names)
                    {
                        output.WriteLine(name);
                    }
                }
            }

            using (var output = new StreamWriter(Path.Combine(listsPath, "files", "status.txt")))
            {
                output.WriteLine("{0}", breakdown);
            }
        }
        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);
                            }
                        }
                    }
                }
            }
        }