Пример #1
0
        public static void Main(string[] args)
        {
            bool showHelp = false;
            bool verbose  = false;
            bool compress = false;

            var options = new OptionSet()
            {
                {
                    "v|verbose",
                    "be verbose",
                    v => verbose = v != null
                },
                {
                    "c|compress",
                    "compress data with LZO1x",
                    v => compress = 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 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ output_fat input_directory+", GetExecutableName());
                Console.WriteLine("Pack files from input directories into a Encapsulated Resource File.");
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            var    inputPaths = new List <string>();
            string fatPath, datPath;

            if (extras.Count == 1)
            {
                inputPaths.Add(extras[0]);
                fatPath = Path.ChangeExtension(extras[0], ".fat");
                datPath = Path.ChangeExtension(extras[0], ".dat");
            }
            else
            {
                fatPath = extras[0];

                if (Path.GetExtension(fatPath) != ".fat")
                {
                    datPath = fatPath;
                    fatPath = Path.ChangeExtension(datPath, ".fat");
                }
                else
                {
                    datPath = Path.ChangeExtension(fatPath, ".dat");
                }

                inputPaths.AddRange(extras.Skip(1));
            }

            var paths = new SortedDictionary <uint, string>();

            if (verbose == true)
            {
                Console.WriteLine("Finding files...");
            }

            foreach (var relPath in inputPaths)
            {
                string inputPath = Path.GetFullPath(relPath);

                if (inputPath.EndsWith(Path.DirectorySeparatorChar.ToString()) == true)
                {
                    inputPath = inputPath.Substring(0, inputPath.Length - 1);
                }

                foreach (string path in Directory.GetFiles(inputPath, "*", SearchOption.AllDirectories))
                {
                    string fullPath = Path.GetFullPath(path);
                    string partPath = fullPath.Substring(inputPath.Length + 1).ToLowerInvariant();

                    uint hash = 0xFFFFFFFFu;
                    if (partPath.ToUpper().StartsWith("__UNKNOWN") == true)
                    {
                        string partName;
                        partName = Path.GetFileNameWithoutExtension(partPath);
                        if (partName.Length > 8)
                        {
                            partName = partName.Substring(0, 8);
                        }

                        hash = uint.Parse(
                            partName,
                            System.Globalization.NumberStyles.AllowHexSpecifier);
                    }
                    else
                    {
                        hash = partPath.HashFileNameCRC32();
                    }

                    if (paths.ContainsKey(hash) == true)
                    {
                        //if (verbose == true)
                        {
                            Console.WriteLine("Ignoring {0} duplicate.", partPath);
                        }

                        continue;
                    }

                    paths[hash] = fullPath;
                    //Console.WriteLine(fullPath);
                }
            }

            var big = new BigFile();

            using (var output = File.Create(datPath))
            {
                foreach (var value in paths)
                {
                    var hash = value.Key;
                    var path = value.Value;

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

                    var entry = new Big.Entry();
                    entry.NameHash = hash;
                    entry.Offset   = output.Position;

                    using (var input = File.OpenRead(path))
                    {
                        if (compress == false)
                        {
                            entry.CompressionScheme = Big.CompressionScheme.None;
                            entry.UncompressedSize  = 0;
                            entry.CompressedSize    = (uint)input.Length;
                            output.WriteFromStream(input, input.Length);
                        }
                        else
                        {
                            var uncompressedData = new byte[input.Length];
                            var uncompressedSize = (uint)uncompressedData.Length;
                            input.Read(uncompressedData, 0, uncompressedData.Length);

                            var compressedData = new byte[
                                uncompressedData.Length +
                                (uncompressedData.Length / 16) + 64 + 3];
                            var compressedSize = (uint)compressedData.Length;

                            var result = LZO1x.Compress(
                                uncompressedData, uncompressedSize,
                                compressedData, ref compressedSize);

                            if (result != 0)
                            {
                                throw new InvalidOperationException("compression error " + result.ToString());
                            }

                            if (compressedSize < uncompressedSize)
                            {
                                entry.CompressionScheme = Big.CompressionScheme.LZO1x;
                                entry.UncompressedSize  = uncompressedSize;
                                entry.CompressedSize    = compressedSize;
                                output.Write(compressedData, 0, (int)compressedSize);
                            }
                            else
                            {
                                input.Seek(0, SeekOrigin.Begin);
                                entry.CompressionScheme = Big.CompressionScheme.None;
                                entry.UncompressedSize  = 0;
                                entry.CompressedSize    = (uint)input.Length;
                                output.WriteFromStream(input, input.Length);
                            }
                        }

                        output.Seek(output.Position.Align(16), SeekOrigin.Begin);
                    }

                    big.Entries.Add(entry);
                }
            }

            using (var output = File.Create(fatPath))
            {
                big.Serialize(output);
            }
        }
Пример #2
0
        public static void Main(string[] args)
        {
            bool showHelp       = false;
            bool overwriteFiles = false;
            bool verbose        = 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> 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_fat [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

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

            var map = new MapFile();

            map.Info          = new Map.Info();
            map.Snapshot      = new Map.Snapshot();
            map.Data          = new Map.Data();
            map.Data.Unknown2 = new Map.Snapshot();
            map.Archive       = new Map.Archive();

            using (var input = File.OpenRead(Path.Combine(inputPath, "map.xml")))
            {
                var doc = new XPathDocument(input);
                var nav = doc.CreateNavigator();

                var info = nav.SelectSingleNode("/map/info");
                map.Info.Name      = info.SelectSingleNode("name").Value;
                map.Info.Creator   = info.SelectSingleNode("creator").Value;
                map.Info.Author    = info.SelectSingleNode("author").Value;
                map.Info.Size      = (Map.Size)Enum.Parse(typeof(Map.Size), info.SelectSingleNode("size").Value);
                map.Info.Players   = (Map.Players)Enum.Parse(typeof(Map.Players), info.SelectSingleNode("players").Value);
                map.Info.Unknown2  = uint.Parse(info.SelectSingleNode("unknown2").Value, CultureInfo.InvariantCulture);
                map.Info.Unknown3  = uint.Parse(info.SelectSingleNode("unknown3").Value, CultureInfo.InvariantCulture);
                map.Info.Unknown4  = uint.Parse(info.SelectSingleNode("unknown4").Value, CultureInfo.InvariantCulture);
                map.Info.Unknown5  = ulong.Parse(info.SelectSingleNode("unknown5").Value, CultureInfo.InvariantCulture);
                map.Info.Unknown7  = ulong.Parse(info.SelectSingleNode("unknown7").Value, CultureInfo.InvariantCulture);
                map.Info.Unknown10 = ulong.Parse(info.SelectSingleNode("unknown10").Value, CultureInfo.InvariantCulture);

                using (var reader = new XmlTextReader(new StringReader(info.SelectSingleNode("unknown11").OuterXml)))
                {
                    reader.MoveToContent();
                    map.Info.Unknown11 = new byte[0];
                    int read = 0;
                    do
                    {
                        Array.Resize(ref map.Info.Unknown11, map.Info.Unknown11.Length + 4096);
                        read += reader.ReadBinHex(map.Info.Unknown11, read, 4096);
                    }while (reader.EOF == false);
                    Array.Resize(ref map.Info.Unknown11, read);
                }

                using (var reader = new XmlTextReader(new StringReader(info.SelectSingleNode("unknown12").OuterXml)))
                {
                    reader.MoveToContent();
                    map.Info.Unknown12 = new byte[0];
                    int read = 0;
                    do
                    {
                        Array.Resize(ref map.Info.Unknown12, map.Info.Unknown12.Length + 4096);
                        read += reader.ReadBinHex(map.Info.Unknown12, read, 4096);
                    }while (reader.EOF == false);
                    Array.Resize(ref map.Info.Unknown12, read);
                }

                map.Info.Unknown15 = uint.Parse(info.SelectSingleNode("unknown15").Value, CultureInfo.InvariantCulture);

                var snapshot = nav.SelectSingleNode("/map/snapshot");
                map.Snapshot.Width         = uint.Parse(snapshot.SelectSingleNode("width").Value, CultureInfo.InvariantCulture);
                map.Snapshot.Height        = uint.Parse(snapshot.SelectSingleNode("height").Value, CultureInfo.InvariantCulture);
                map.Snapshot.BytesPerPixel = uint.Parse(snapshot.SelectSingleNode("bpp").Value, CultureInfo.InvariantCulture);
                map.Snapshot.Unknown4      = uint.Parse(snapshot.SelectSingleNode("unknown4").Value, CultureInfo.InvariantCulture);

                var data = nav.SelectSingleNode("/map/data");
                map.Data.Unknown1 = data.SelectSingleNode("unknown1").Value;
            }

            using (var input = File.OpenRead(Path.Combine(inputPath, "snapshot.bin")))
            {
                map.Snapshot.Data = new byte[input.Length];
                input.Read(map.Snapshot.Data, 0, map.Snapshot.Data.Length);
            }

            var paths = new SortedDictionary <uint, string>();

            if (verbose == true)
            {
                Console.WriteLine("Finding files...");
            }

            var dataPath = Path.Combine(inputPath, "archive");

            dataPath = Path.GetFullPath(dataPath);

            if (dataPath.EndsWith(Path.DirectorySeparatorChar.ToString()) == true)
            {
                dataPath = dataPath.Substring(0, dataPath.Length - 1);
            }

            foreach (string path in Directory.GetFiles(dataPath, "*", SearchOption.AllDirectories))
            {
                string fullPath = Path.GetFullPath(path);
                string partPath = fullPath.Substring(dataPath.Length + 1).ToLowerInvariant();

                uint hash = 0xFFFFFFFFu;
                if (partPath.ToUpper().StartsWith("__UNKNOWN") == true)
                {
                    string partName;
                    partName = Path.GetFileNameWithoutExtension(partPath);
                    if (partName.Length > 8)
                    {
                        partName = partName.Substring(0, 8);
                    }

                    hash = uint.Parse(
                        partName,
                        System.Globalization.NumberStyles.AllowHexSpecifier);
                }
                else
                {
                    hash = partPath.HashFileNameCRC32();
                }

                if (paths.ContainsKey(hash) == true)
                {
                    continue;
                }

                paths[hash] = fullPath;
            }

            var big = new BigFile();

            using (var output = new MemoryStream())
            {
                foreach (var value in paths)
                {
                    var hash = value.Key;
                    var path = value.Value;

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

                    var entry = new Big.Entry();
                    entry.NameHash = hash;
                    entry.Offset   = output.Position;

                    using (var input = File.OpenRead(path))
                    {
                        entry.CompressionScheme = Big.CompressionScheme.None;
                        entry.UncompressedSize  = 0;
                        entry.CompressedSize    = (uint)input.Length;
                        output.WriteFromStream(input, input.Length);
                    }

                    big.Entries.Add(entry);
                }

                map.Archive.DAT = MakeCompressedData(output);
            }

            using (var output = new MemoryStream())
            {
                big.Serialize(output);
                map.Archive.FAT = MakeCompressedData(output);
            }

            using (var output = new MemoryStream())
            {
                var settings = new XmlWriterSettings();
                settings.Indent             = true;
                settings.OmitXmlDeclaration = true;
                settings.IndentChars        = "\t";
                settings.Encoding           = Encoding.ASCII;

                using (var writer = XmlWriter.Create(output, settings))
                {
                    writer.WriteStartDocument();
                    writer.WriteStartElement("FatInfo");

                    foreach (var entry in big.Entries.OrderBy(e => e.NameHash))
                    {
                        writer.WriteStartElement("File");
                        writer.WriteAttributeString("Path", entry.NameHash.ToString(CultureInfo.InvariantCulture));
                        writer.WriteAttributeString("Crc", entry.NameHash.ToString(CultureInfo.InvariantCulture));
                        writer.WriteAttributeString("FileTime", "0");
                        writer.WriteEndElement();
                    }

                    writer.WriteEndElement();
                    writer.WriteEndDocument();
                }

                output.Position = 0;
                map.Archive.XML = MakeCompressedData(output);
            }

            using (var output = File.Create(outputPath))
            {
                map.Serialize(output);
            }
        }
Пример #3
0
        private static void Main(string[] args)
        {
            bool showHelp = false;
            bool verbose  = false;
            bool compress = false;

            int packageVersion = 9;

            Big.Platform packagePlatform = Big.Platform.PC;

            var options = new OptionSet()
            {
                { "v|verbose", "be verbose", v => verbose = v != null },
                { "c|compress", "compress data with LZO1x", v => compress = v != null },
                { "pv|package-version", "package version (default 9)", v => packageVersion = ParsePackageVersion(v) },
                { "pp|package-platform", "package platform (default PC)", v => packagePlatform = ParsePackagePlatform(v) },
                { "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 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ output_fat input_directory+", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Pack files from input directories into a Big File (FAT/DAT pair).");
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            var    inputPaths = new List <string>();
            string fatPath, datPath;

            if (extras.Count == 1)
            {
                inputPaths.Add(extras[0]);
                fatPath = Path.ChangeExtension(extras[0], ".fat");
                datPath = Path.ChangeExtension(extras[0], ".dat");
            }
            else
            {
                fatPath = extras[0];

                if (Path.GetExtension(fatPath) != ".fat")
                {
                    datPath = fatPath;
                    fatPath = Path.ChangeExtension(datPath, ".fat");
                }
                else
                {
                    datPath = Path.ChangeExtension(fatPath, ".dat");
                }

                inputPaths.AddRange(extras.Skip(1));
            }

            var pendingEntries = new SortedDictionary <ulong, PendingEntry>();

            if (verbose == true)
            {
                Console.WriteLine("Finding files...");
            }

            foreach (var relativePath in inputPaths)
            {
                string inputPath = Path.GetFullPath(relativePath);

                if (inputPath.EndsWith(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture)) == true)
                {
                    inputPath = inputPath.Substring(0, inputPath.Length - 1);
                }

                foreach (string path in Directory.GetFiles(inputPath, "*", SearchOption.AllDirectories))
                {
                    PendingEntry pendingEntry;

                    string fullPath = Path.GetFullPath(path);
                    string partPath = fullPath.Substring(inputPath.Length + 1).ToLowerInvariant();

                    pendingEntry.FullPath = fullPath;
                    pendingEntry.PartPath = partPath;

                    var pieces = partPath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
                    int index  = 0;

                    if (index >= pieces.Length)
                    {
                        continue;
                    }

                    if (pieces[index].ToUpperInvariant() == "__SUBFAT")
                    {
                        Console.WriteLine("Sorry, packing of subfats is not currently implemented.");
                        return;
                    }

                    if (index >= pieces.Length)
                    {
                        continue;
                    }

                    if (pieces[index].ToUpperInvariant() == "__UNKNOWN")
                    {
                        var partName = Path.GetFileNameWithoutExtension(partPath);

                        if (string.IsNullOrEmpty(partName) == true)
                        {
                            continue;
                        }

                        if (partName.Length > 16)
                        {
                            partName = partName.Substring(0, 16);
                        }

                        pendingEntry.Name     = null;
                        pendingEntry.NameHash = ulong.Parse(partName, NumberStyles.AllowHexSpecifier);
                    }
                    else
                    {
                        pendingEntry.Name     = string.Join("\\", pieces.Skip(index).ToArray()).ToLowerInvariant();
                        pendingEntry.NameHash = CRC64.Hash(pendingEntry.Name);
                    }

                    if (pendingEntries.ContainsKey(pendingEntry.NameHash) == true)
                    {
                        Console.WriteLine("Ignoring duplicate of {0:X}: {1}", pendingEntry.NameHash, partPath);

                        if (verbose == true)
                        {
                            Console.WriteLine("  Previously added from: {0}",
                                              pendingEntries[pendingEntry.NameHash].PartPath);
                        }

                        continue;
                    }

                    pendingEntries[pendingEntry.NameHash] = pendingEntry;
                }
            }

            var fat = new BigFile
            {
                Version  = packageVersion,
                Platform = packagePlatform
            };

            // reasonable default?
            // need to figure out what this value actually does
            if (packagePlatform == Big.Platform.PC)
            {
                fat.Unknown74 = 3;
            }
            else if (packagePlatform == Big.Platform.PS3 ||
                     packagePlatform == Big.Platform.X360)
            {
                fat.Unknown74 = 4;
            }
            else
            {
                throw new InvalidOperationException();
            }

            using (var output = File.Create(datPath))
            {
                long current = 0;
                long total   = pendingEntries.Count;
                var  padding = total.ToString(CultureInfo.InvariantCulture).Length;

                foreach (var pendingEntry in pendingEntries.Select(kv => kv.Value))
                {
                    current++;

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

                    var entry = new Big.Entry();
                    entry.NameHash = pendingEntry.NameHash;
                    entry.Offset   = output.Position;

                    using (var input = File.OpenRead(pendingEntry.FullPath))
                    {
                        EntryCompression.Compress(fat.Platform, ref entry, input, compress, output);
                        output.Seek(output.Position.Align(16), SeekOrigin.Begin);
                    }

                    fat.Entries.Add(entry);
                }
            }

            using (var output = File.Create(fatPath))
            {
                fat.Serialize(output);
            }
        }
Пример #4
0
        public static void Main(string[] args)
        {
            bool showHelp = false;
            bool verbose  = false;

            OptionSet 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> 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 || showHelp == true)
            {
                Console.WriteLine("Usage: {0} [OPTIONS]+ output_big input_directory+", GetExecutableName());
                Console.WriteLine("Pack files from input directories into a Big File.");
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            var    inputPaths = new List <string>();
            string outputPath;

            if (extras.Count == 1)
            {
                inputPaths.Add(extras[0]);
                outputPath = Path.ChangeExtension(extras[0], ".viv");
            }
            else
            {
                outputPath = extras[0];
                inputPaths.AddRange(extras.Skip(1));
            }

            var paths = new SortedDictionary <uint, string>();

            if (verbose == true)
            {
                Console.WriteLine("Finding files...");
            }

            foreach (var relPath in inputPaths)
            {
                string inputPath = Path.GetFullPath(relPath);

                if (inputPath.EndsWith(Path.DirectorySeparatorChar.ToString()) == true)
                {
                    inputPath = inputPath.Substring(0, inputPath.Length - 1);
                }

                foreach (string path in Directory.GetFiles(inputPath, "*", SearchOption.AllDirectories))
                {
                    string fullPath = Path.GetFullPath(path);
                    string partPath = fullPath.Substring(inputPath.Length + 1).ToLowerInvariant();

                    uint hash = 0xFFFFFFFF;
                    if (partPath.ToUpper().StartsWith("__UNKNOWN") == true)
                    {
                        string partName;

                        partName = Path.GetFileNameWithoutExtension(partPath);
                        if (partName.Length > 8)
                        {
                            partName = partName.Substring(0, 8);
                        }

                        hash = uint.Parse(
                            partName,
                            System.Globalization.NumberStyles.AllowHexSpecifier);
                    }
                    else
                    {
                        hash = partPath.ToLowerInvariant().HashFileName();
                    }

                    if (paths.ContainsKey(hash) == true)
                    {
                        Console.WriteLine("Ignoring {0} duplicate.", partPath);
                        continue;
                    }

                    paths[hash] = fullPath;
                }
            }

            using (var output = File.Open(outputPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
            {
                var big = new BigFile();

                if (verbose == true)
                {
                    Console.WriteLine("Adding files...");
                }

                // write a dummy header
                output.Seek(0, SeekOrigin.Begin);
                big.Entries.Clear();
                foreach (var kvp in paths)
                {
                    big.Entries.Add(new BigFile.Entry()
                    {
                        Name   = 0,
                        Offset = 0,
                        Size   = 0,
                    });
                }
                big.Serialize(output);

                output.Seek(output.Position.Align(2048), SeekOrigin.Begin);
                long baseOffset = output.Position;

                // write file data
                big.Entries.Clear();

                if (verbose == true)
                {
                    Console.WriteLine("Writing to disk...");
                }

                foreach (var kvp in paths)
                {
                    if (verbose == true)
                    {
                        Console.WriteLine(kvp.Value);
                    }

                    using (var input = File.Open(kvp.Value, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                    {
                        output.Seek(baseOffset, SeekOrigin.Begin);

                        uint size = (uint)input.Length.Align(2048);

                        big.Entries.Add(new BigFile.Entry()
                        {
                            Name   = kvp.Key,
                            Offset = (uint)output.Position,
                            Size   = size,
                        });

                        output.WriteFromStream(input, input.Length);
                        baseOffset += size;
                    }
                }

                // write filled header
                output.Seek(0, SeekOrigin.Begin);
                big.TotalFileSize = (uint)output.Length;
                big.Serialize(output);
            }
        }
Пример #5
0
        public static void Main(string[] args)
        {
            bool   showHelp        = false;
            bool   extractUnknowns = true;
            bool   extractFiles    = true;
            bool   extractSubFats  = true;
            bool   unpackSubFats   = false;
            string filterPattern   = null;
            bool   overwriteFiles  = false;
            bool   verbose         = false;

            var options = new OptionSet()
            {
                { "o|overwrite", "overwrite existing files", v => overwriteFiles = v != null },
                { "nf|no-files", "don't extract files", v => extractFiles = v == null },
                { "nu|no-unknowns", "don't extract unknown files", v => extractUnknowns = v == null },
                { "ns|no-subfats", "don't extract subfats", v => extractSubFats = v == null },
                { "us|unpack-subfats", "unpack files from subfats", v => unpackSubFats = 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 },
            };

            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_fat [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Unpack files from a Big File (FAT/DAT pair).");
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

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

            Regex filter = null;

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

            if (Path.GetExtension(fatPath) == ".dat")
            {
                datPath = fatPath;
                fatPath = Path.ChangeExtension(fatPath, ".fat");
            }
            else
            {
                datPath = Path.ChangeExtension(fatPath, ".dat");
            }

            if (verbose == true)
            {
                Console.WriteLine("Loading project...");
            }

            var manager = ProjectData.Manager.Load();

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

            if (verbose == true)
            {
                Console.WriteLine("Reading FAT...");
            }

            BigFile fat;

            using (var input = File.OpenRead(fatPath))
            {
                fat = new BigFile();
                fat.Deserialize(input);
            }

            var hashes       = manager.LoadListsFileNames(fat.Version);
            var subFatHashes = manager.LoadListsSubFatNames(fat.Version);

            using (var input = File.OpenRead(datPath))
            {
                if (extractFiles == true)
                {
                    Big.Entry[] entries;
                    if (extractSubFats == true &&
                        unpackSubFats == true)
                    {
                        entries =
                            fat.Entries.Concat(fat.SubFats.SelectMany(sf => sf.Entries))
                            .OrderBy(e => e.Offset)
                            .ToArray();
                    }
                    else
                    {
                        entries = fat.Entries.OrderBy(e => e.Offset).ToArray();
                    }

                    if (entries.Length > 0)
                    {
                        if (verbose == true)
                        {
                            Console.WriteLine("Unpacking files...");
                        }

                        long current = 0;
                        long total   = entries.Length;
                        var  padding = total.ToString(CultureInfo.InvariantCulture).Length;

                        var duplicates = new Dictionary <ulong, int>();

                        foreach (var entry in entries)
                        {
                            current++;

                            if (subFatHashes.Contains(entry.NameHash) == true)
                            {
                                continue;
                            }

                            string entryName;
                            if (GetEntryName(input, fat, entry, hashes, extractUnknowns, out entryName) == false)
                            {
                                continue;
                            }

                            if (duplicates.ContainsKey(entry.NameHash) == true)
                            {
                                var number = duplicates[entry.NameHash]++;
                                var e      = Path.GetExtension(entryName);
                                var nn     =
                                    Path.ChangeExtension(
                                        Path.ChangeExtension(entryName, null) + "__DUPLICATE_" +
                                        number.ToString(CultureInfo.InvariantCulture),
                                        e);
                                entryName = Path.Combine("__DUPLICATE", nn);
                            }
                            else
                            {
                                duplicates[entry.NameHash] = 0;
                            }

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

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

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

                            input.Seek(entry.Offset, SeekOrigin.Begin);

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

                            using (var output = File.Create(entryPath))
                            {
                                EntryDecompression.Decompress(entry, input, output);
                            }
                        }
                    }
                }

                if (extractSubFats == true &&
                    unpackSubFats == false &&
                    fat.SubFats.Count > 0)
                {
                    if (verbose == true)
                    {
                        Console.WriteLine("Unpacking subfats...");
                    }

                    var subFatsFromFat = fat.SubFats.ToList();

                    long current = 0;
                    long total   = subFatsFromFat.Count;
                    var  padding = total.ToString(CultureInfo.InvariantCulture).Length;

                    foreach (var headerEntry in fat.Entries.Where(e => subFatHashes.Contains(e.NameHash) == true))
                    {
                        current++;

                        var subFat = new SubFatFile();
                        using (var temp = new MemoryStream())
                        {
                            EntryDecompression.Decompress(headerEntry, input, temp);
                            temp.Position = 0;
                            subFat.Deserialize(temp, fat);
                        }

                        var matchingSubFats = subFatsFromFat
                                              .Where(sf => subFat.Entries.SequenceEqual(sf.Entries))
                                              .ToArray();

                        if (matchingSubFats.Length == 0)
                        {
                            continue;
                        }

                        if (matchingSubFats.Length > 1)
                        {
                            throw new InvalidOperationException();
                        }

                        var entryName = subFatHashes[headerEntry.NameHash];
                        entryName = FilterEntryName(entryName);

                        var entryHeaderPath = Path.Combine(outputPath, "__SUBFAT", entryName);
                        if (overwriteFiles == false &&
                            File.Exists(entryHeaderPath) == true)
                        {
                            continue;
                        }

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

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

                        var entryDataPath = Path.ChangeExtension(entryHeaderPath, ".dat");

                        var rebuiltFat = new BigFile
                        {
                            Version   = fat.Version,
                            Platform  = fat.Platform,
                            Unknown74 = fat.Unknown74
                        };

                        using (var output = File.Create(entryDataPath))
                        {
                            var rebuiltEntries = new List <Big.Entry>();
                            foreach (var entry in subFat.Entries.OrderBy(e => e.Offset))
                            {
                                var rebuiltEntry = new Big.Entry
                                {
                                    NameHash          = entry.NameHash,
                                    UncompressedSize  = entry.UncompressedSize,
                                    CompressedSize    = entry.CompressedSize,
                                    Offset            = output.Position,
                                    CompressionScheme = entry.CompressionScheme
                                };

                                input.Seek(entry.Offset, SeekOrigin.Begin);
                                output.WriteFromStream(input, entry.CompressedSize);
                                output.Seek(output.Position.Align(16), SeekOrigin.Begin);

                                rebuiltEntries.Add(rebuiltEntry);
                            }
                            rebuiltFat.Entries.AddRange(rebuiltEntries.OrderBy(e => e.NameHash));
                        }

                        using (var output = File.Create(entryHeaderPath))
                        {
                            rebuiltFat.Serialize(output);
                        }

                        foreach (var matchingSubFat in matchingSubFats)
                        {
                            subFatsFromFat.Remove(matchingSubFat);
                        }
                    }

                    if (subFatsFromFat.Count > 0)
                    {
                        Console.WriteLine("Warning: could not identify {0} subfats", subFatsFromFat.Count);
                    }
                }
            }
        }