예제 #1
0
        public static void Main(string[] args)
        {
            bool showHelp = false;
            bool extractUnknowns = true;
            bool overwriteFiles = 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
                },
                {
                    "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_sfar [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

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

            var manager = ProjectData.Manager.Load();
            if (manager.ActiveProject == null)
            {
                Console.WriteLine("Warning: no active project loaded.");
            }
            var hashes = manager.LoadLists(
                "*.filelist",
                FileNameHash.Compute,
                s => s.Replace("\\", "/"));

            using (var input = File.OpenRead(inputPath))
            {
                var sfx = new SFXArchiveFile();
                sfx.Deserialize(input);

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

                if (sfx.CompressionScheme != SFXArchive.CompressionScheme.None &&
                    sfx.CompressionScheme != SFXArchive.CompressionScheme.Lzma &&
                    sfx.CompressionScheme != SFXArchive.CompressionScheme.Lzx)
                {
                    Console.WriteLine("Unsupported compression scheme!");
                    return;
                }

                var inputBlock = new byte[sfx.MaximumBlockSize];
                var outputBlock = new byte[sfx.MaximumBlockSize];

                var hashesFromFile = new Dictionary<FileNameHash, string>();

                // todo: figure out what the file name is
                var fileNameListNameHash = new FileNameHash(
                    new byte[] { 0xB5, 0x50, 0x19, 0xCB, 0xF9, 0xD3, 0xDA, 0x65, 0xD5, 0x5B, 0x32, 0x1C, 0x00, 0x19, 0x69, 0x7C, });
                var fileNameListEntry = sfx.Entries
                    .FirstOrDefault(e =>
                        e.NameHash == fileNameListNameHash);
                if (fileNameListEntry != null)
                {
                    using (var temp = new MemoryStream())
                    {
                        DecompressEntry(
                            sfx, fileNameListEntry,
                            input, inputBlock,
                            temp, outputBlock);
                        temp.Position = 0;

                        var reader = new StreamReader(temp);
                        while (reader.EndOfStream == false)
                        {
                            var line = reader.ReadLine();
                            hashesFromFile.Add(FileNameHash.Compute(line), line);
                        }
                    }
                }

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

                    var entryName = hashes[entry.NameHash];
                    
                    if (entryName == null)
                    {
                        if (hashesFromFile.ContainsKey(entry.NameHash) == true)
                        {
                            entryName = hashesFromFile[entry.NameHash];
                        }
                    }

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

                        entryName = entry.NameHash.ToString();
                        entryName = Path.Combine("__UNKNOWN", entryName);
                    }
                    else
                    {
                        entryName = entryName.Replace("/", "\\");
                        if (entryName.StartsWith("\\") == true)
                        {
                            entryName = entryName.Substring(1);
                        }
                    }

                    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);

                    Directory.CreateDirectory(Path.GetDirectoryName(entryPath));
                    using (var output = File.Create(entryPath))
                    {
                        DecompressEntry(
                            sfx, entry,
                            input, inputBlock,
                            output, outputBlock);
                    }
                }
            }
        }
예제 #2
0
        private static void DecompressEntry(
            SFXArchiveFile sfx,
            SFXArchive.Entry entry,
            Stream input,
            byte[] inputBlock,
            Stream output,
            byte[] outputBlock)
        {
            var left = entry.UncompressedSize;
            input.Seek(entry.Offset, SeekOrigin.Begin);

            if (entry.BlockSizeIndex == -1)
            {
                output.WriteFromStream(input, entry.UncompressedSize);
            }
            else
            {
                var blockSizeIndex = entry.BlockSizeIndex;
                while (left > 0)
                {
                    var compressedBlockSize = sfx.BlockSizes[blockSizeIndex];
                    if (compressedBlockSize == 0)
                    {
                        compressedBlockSize = sfx.MaximumBlockSize;
                    }

                    if (sfx.CompressionScheme == SFXArchive.CompressionScheme.None)
                    {
                        output.WriteFromStream(input, compressedBlockSize);
                        left -= compressedBlockSize;
                    }
                    else if (sfx.CompressionScheme == SFXArchive.CompressionScheme.Lzma)
                    {
                        if (compressedBlockSize == sfx.MaximumBlockSize ||
                            compressedBlockSize == left)
                        {
                            output.WriteFromStream(input, compressedBlockSize);
                            left -= compressedBlockSize;
                        }
                        else
                        {
                            var uncompressedBlockSize = (uint)Math.Min(
                                left, sfx.MaximumBlockSize);

                            if (compressedBlockSize < 5)
                            {
                                throw new InvalidOperationException();
                            }

                            var properties = input.ReadBytes(5);
                            compressedBlockSize -= 5;

                            if (input.Read(inputBlock, 0, (int)compressedBlockSize)
                                != compressedBlockSize)
                            {
                                throw new EndOfStreamException();
                            }

                            uint actualUncompressedBlockSize = uncompressedBlockSize;
                            uint actualCompressedBlockSize = compressedBlockSize;

                            var error = LZMA.Decompress(
                                outputBlock,
                                ref actualUncompressedBlockSize,
                                inputBlock,
                                ref actualCompressedBlockSize,
                                properties,
                                (uint)properties.Length);

                            if (error != LZMA.ErrorCode.Ok ||
                                uncompressedBlockSize != actualUncompressedBlockSize ||
                                compressedBlockSize != actualCompressedBlockSize)
                            {
                                throw new InvalidOperationException();
                            }

                            output.Write(outputBlock, 0, (int)actualUncompressedBlockSize);
                            left -= uncompressedBlockSize;
                        }
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }

                    blockSizeIndex++;
                }
            }
        }