public static void Main(string[] args)
        {
            bool   showHelp        = false;
            bool?  extractUnknowns = null;
            bool   overwriteFiles  = false;
            bool   verbose         = true;
            string currentProject  = null;
            string filterPattern   = 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 ? false : extractUnknowns
                },
                {
                    "ou|only-unknowns", "only extract unknown files",
                    v => extractUnknowns = v != null ? true : extractUnknowns
                },
                { "f|filter=", "filter 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 ||
                Is000(extras[0]) == false)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ input_file.000.tiger [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            var inputPath  = extras[0];
            var basePath   = GetBasePath(inputPath);
            var outputPath = extras.Count > 1 ? extras[1] : basePath + "_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 header = new TigerArchiveFile();

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

            var hashes = manager.LoadLists("*.filelist", s => s.HashFileName(), s => s.ToLowerInvariant());

            Directory.CreateDirectory(outputPath);

            var settings = new XmlWriterSettings()
            {
                Indent = true,
            };

            var xmlPath = Path.Combine(outputPath, "@tiger.xml");

            using (var xml = XmlWriter.Create(xmlPath, settings))
            {
                xml.WriteStartDocument();
                xml.WriteStartElement("files");
                xml.WriteAttributeString("endian", header.Endian.ToString());
                xml.WriteAttributeString("basepath", header.BasePath);
                xml.WriteAttributeString("priority", header.Priority.ToString(CultureInfo.InvariantCulture));

                Stream data       = null;
                byte?  dataIndex  = null;
                uint?  lastLocale = null;
                {
                    long current = 0;
                    long total   = header.Entries.Count;

                    //long? lastOffset = null;

                    foreach (var entry in header.Entries.OrderBy(e => e.Offset).ThenBy(e => e.DataIndex))
                    {
                        current++;

                        if (dataIndex.HasValue == false || dataIndex.Value != entry.DataIndex)
                        {
                            if (data != null)
                            {
                                data.Close();
                            }

                            dataIndex = entry.DataIndex;

                            var dataPath = basePath + GetExtension(dataIndex.Value);

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

                            data = File.OpenRead(dataPath);
                        }

                        string name = hashes[entry.NameHash];

                        if (name == null)
                        {
                            if (extractUnknowns.HasValue == true && extractUnknowns.Value == false)
                            {
                                continue;
                            }

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

                                if (entry.Size > 0)
                                {
                                    data.Position = entry.Offset;
                                    read          = data.Read(guess, 0, (int)Math.Min(entry.Size, guess.Length));
                                }

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

                            name = entry.NameHash.ToString("X8", CultureInfo.InvariantCulture);
                            name = Path.ChangeExtension(name, "." + extension);
                            name = Path.Combine(extension, name);
                            name = Path.Combine("__UNKNOWN", name);
                        }
                        else
                        {
                            if (extractUnknowns.HasValue == true && extractUnknowns.Value == true)
                            {
                                continue;
                            }

                            name = name.Replace("/", "\\");
                            if (name.StartsWith("\\") == true)
                            {
                                name = name.Substring(1);
                            }
                        }

                        if (entry.Locale == 0xFFFFFFFF)
                        {
                            name = Path.Combine("default", name);
                        }
                        else
                        {
                            name = Path.Combine(entry.Locale.ToString("X8"), name);
                        }

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

                        var entryPath = Path.Combine(outputPath, name);

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

                        if (lastLocale.HasValue == false || lastLocale.Value != entry.Locale)
                        {
                            xml.WriteComment(string.Format(
                                                 " {0} = {1} ",
                                                 entry.Locale.ToString("X8", CultureInfo.InvariantCulture),
                                                 ((ArchiveLocale)entry.Locale)));
                            lastLocale = entry.Locale;
                        }

                        /*
                         * if (lastOffset != null)
                         * {
                         *  xml.WriteComment(string.Format(" padding = {0} @ {1}",
                         *                                 entry.Offset - lastOffset.Value,
                         *                                 lastOffset.Value));
                         * }
                         * lastOffset = entry.Offset + entry.Size;
                         *
                         * xml.WriteComment(string.Format(" offset = {0} ", entry.Offset));
                         */

                        xml.WriteStartElement("entry");
                        xml.WriteAttributeString("hash", entry.NameHash.ToString("X8", CultureInfo.InvariantCulture));
                        xml.WriteAttributeString("locale", entry.Locale.ToString("X8", CultureInfo.InvariantCulture));

                        if (entry.Priority != header.Priority)
                        {
                            xml.WriteAttributeString("priority", entry.Priority.ToString(CultureInfo.InvariantCulture));
                        }

                        xml.WriteValue(name);
                        xml.WriteEndElement();

                        if (overwriteFiles == false && File.Exists(entryPath) == true)
                        {
                            continue;
                        }

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

                        using (var output = File.Create(entryPath))
                        {
                            if (entry.Size > 0)
                            {
                                data.Position = entry.Offset;
                                output.WriteFromStream(data, entry.Size);
                            }
                        }
                    }
                }

                if (data != null)
                {
                    data.Close();
                }

                xml.WriteEndElement();
                xml.WriteEndDocument();
                xml.Flush();
            }
        }
        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 = 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.LoadLists("*.filelist",
                                            s => s.HashFileName(),
                                            s => s.ToLowerInvariant());

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

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

            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, "*.000.tiger", SearchOption.AllDirectories));

            var outputPaths = new List <string>();

            var breakdown = new Breakdown();

            Console.WriteLine("Processing...");
            foreach (var inputPath in inputPaths)
            {
                var outputPath = GetListPath(installPath, Path.ChangeExtension(inputPath, null));
                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 big = new TigerArchiveFile();
                if (File.Exists(inputPath + ".bak") == true)
                {
                    using (var input = File.OpenRead(inputPath + ".bak"))
                    {
                        big.Deserialize(input);
                    }
                }
                else
                {
                    using (var input = File.OpenRead(inputPath))
                    {
                        big.Deserialize(input);
                    }
                }

                var localBreakdown = new Breakdown();

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

                    localBreakdown.Total++;
                }

                names.Sort();

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

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

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

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

            using (var output = new StreamWriter(Path.Combine(Path.Combine(listsPath, "files"), "status.txt")))
            {
                output.WriteLine("{0}", breakdown);
            }
        }