Exemplo n.º 1
0
        public static void Main(string[] args)
        {
            bool showHelp       = false;
            bool overwriteFiles = false;
            bool noCrypto       = false;
            bool verbose        = false;

            var options = new OptionSet()
            {
                { "no-crypto", "don't use any encryption", v => noCrypto = v != null },
                { "o|overwrite", "overwrite existing files", v => overwriteFiles = 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_ipf [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

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

            const Endian endian = Endian.Little;

            using (var input = File.OpenRead(inputPath))
            {
                if (input.Length < ArchiveHeader.Size)
                {
                    throw new FormatException();
                }

                input.Seek(-ArchiveHeader.Size, SeekOrigin.End);

                var header = ArchiveHeader.Read(input, endian);
                if (header.Magic != ArchiveHeader.Signature)
                {
                    throw new FormatException();
                }

                var fileEntries = new ArchiveFileEntry[header.FileTableCount];
                if (header.FileTableCount > 0)
                {
                    input.Position = header.FileTableOffset;
                    for (int i = 0; i < header.FileTableCount; i++)
                    {
                        fileEntries[i] = ArchiveFileEntry.Read(input, endian);
                    }
                }

                var deletionEntries = new ArchiveDeletionEntry[header.DeletionTableCount];
                if (header.DeletionTableCount > 0)
                {
                    input.Position = header.DeletionTableOffset;
                    for (int i = 0; i < header.DeletionTableCount; i++)
                    {
                        deletionEntries[i] = ArchiveDeletionEntry.Read(input, endian);
                    }
                }

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

                if (header.DeletionTableCount > 0)
                {
                    Directory.CreateDirectory(outputPath);

                    using (var output = File.Create(Path.Combine(outputPath, "deletions.json")))
                        using (var streamWriter = new StreamWriter(output))
                            using (var writer = new JsonTextWriter(streamWriter))
                            {
                                writer.Indentation = 2;
                                writer.IndentChar  = ' ';
                                writer.Formatting  = Formatting.Indented;
                                writer.WriteStartArray();
                                foreach (var deletionEntry in deletionEntries)
                                {
                                    writer.WriteStartObject();
                                    writer.WritePropertyName("archive");
                                    writer.WriteValue(deletionEntry.Archive);
                                    writer.WritePropertyName("name");
                                    writer.WriteValue(deletionEntry.Name);
                                    writer.WriteEndObject();
                                }
                                writer.WriteEndArray();
                            }
                }

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

                    var entryPath = Path.Combine(outputPath,
                                                 entry.Archive.Replace('/', Path.DirectorySeparatorChar),
                                                 entry.Name.Replace('/', Path.DirectorySeparatorChar));
                    if (overwriteFiles == false && File.Exists(entryPath) == true)
                    {
                        continue;
                    }

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

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

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

                    using (var output = File.Create(entryPath))
                    {
                        input.Seek(entry.Offset, SeekOrigin.Begin);

                        if (entry.ShouldCompress == false)
                        {
                            output.WriteFromStream(input, entry.CompressedSize);
                        }
                        else
                        {
                            var bytes = input.ReadBytes(entry.CompressedSize);

                            if (noCrypto == false)
                            {
                                var crypto = new ArchiveCrypto();
                                crypto.Decrypt(bytes, 0, bytes.Length);
                            }

                            using (var temp = new MemoryStream(bytes, false))
                            {
                                var zlib = new InflaterInputStream(temp, new Inflater(true));
                                output.WriteFromStream(zlib, entry.UncompressedSize);
                            }
                        }
                    }
                }
            }
        }
Exemplo n.º 2
0
        public static void Main(string[] args)
        {
            bool showHelp = false;
            bool verbose  = false;
            bool noCrypto = false;

            var options = new OptionSet()
            {
                { "no-crypto", "don't use any encryption", v => noCrypto = 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_dir [output_dir]", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Options:");
                options.WriteOptionDescriptions(Console.Out);
                return;
            }

            const Endian endian = Endian.Little;

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

            var archiveInfosUnsorted = new List <ArchiveInfo>();

            var basePathInfo = new List <KeyValuePair <string, string> >();

            basePathInfo.Add(new KeyValuePair <string, string>(Path.Combine(inputPath, "data"), "*.ipf"));
            basePathInfo.Add(new KeyValuePair <string, string>(Path.Combine(inputPath, "patch"), "*_001001.ipf"));

            foreach (var kv in basePathInfo)
            {
                var basePath = kv.Key;
                var filter   = kv.Value;

                if (Directory.Exists(basePath) == false)
                {
                    continue;
                }

                foreach (var archivePath in Directory.GetFiles(basePath, filter))
                {
                    using (var input = File.OpenRead(archivePath))
                    {
                        if (input.Length < ArchiveHeader.Size)
                        {
                            throw new FormatException();
                        }

                        input.Seek(-ArchiveHeader.Size, SeekOrigin.End);

                        var header = ArchiveHeader.Read(input, endian);
                        if (header.Magic != ArchiveHeader.Signature)
                        {
                            throw new FormatException();
                        }

                        archiveInfosUnsorted.Add(
                            new ArchiveInfo(archivePath,
                                            header.BaseRevision,
                                            header.Revision,
                                            header.FileTableCount + header.DeletionTableCount));
                    }
                }
            }

            var archiveInfos = archiveInfosUnsorted.OrderBy(ai => ai.BaseRevision)
                               .ThenBy(ai => ai.Revision)
                               .ThenBy(ai => ai.Path)
                               .ToList();

            long current = 0;
            long total   = archiveInfos.Sum(ai => ai.TotalCount);
            var  padding = total.ToString(CultureInfo.InvariantCulture).Length;

            var dataPaths = new Dictionary <string, List <string> >();

            foreach (var archiveInfo in archiveInfos)
            {
                using (var input = File.OpenRead(archiveInfo.Path))
                {
                    if (input.Length < ArchiveHeader.Size)
                    {
                        throw new FormatException();
                    }

                    input.Seek(-ArchiveHeader.Size, SeekOrigin.End);

                    var header = ArchiveHeader.Read(input, endian);
                    if (header.Magic != ArchiveHeader.Signature)
                    {
                        throw new FormatException();
                    }

                    var fileEntries = new ArchiveFileEntry[header.FileTableCount];
                    if (header.FileTableCount > 0)
                    {
                        input.Position = header.FileTableOffset;
                        for (int i = 0; i < header.FileTableCount; i++)
                        {
                            fileEntries[i] = ArchiveFileEntry.Read(input, endian);
                        }
                    }

                    var deletionEntries = new ArchiveDeletionEntry[header.DeletionTableCount];
                    if (header.DeletionTableCount > 0)
                    {
                        input.Position = header.DeletionTableOffset;
                        for (int i = 0; i < header.DeletionTableCount; i++)
                        {
                            deletionEntries[i] = ArchiveDeletionEntry.Read(input, endian);
                        }
                    }

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

                        if (entry.Archive == "data")
                        {
                            var dataPath = entry.Name.ToLowerInvariant();

                            if (dataPaths.ContainsKey(dataPath) == false)
                            {
                                // probably an incorrect entry pointing to a directory
                                continue;
                            }

                            foreach (var entryPath in dataPaths[dataPath])
                            {
                                if (File.Exists(entryPath) == true)
                                {
                                    File.Delete(entryPath);
                                }
                            }

                            dataPaths.Remove(entry.Name);
                        }
                        else
                        {
                            throw new NotImplementedException();

                            var entryPath = Path.Combine(outputPath, entry.Archive, entry.Name);
                            if (File.Exists(entryPath) == true)
                            {
                                File.Delete(entryPath);
                            }
                        }
                    }

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

                        var entryPath = Path.Combine(outputPath,
                                                     entry.Archive.Replace('/', Path.DirectorySeparatorChar),
                                                     entry.Name.Replace('/', Path.DirectorySeparatorChar));

                        var dataPath = entry.Name.ToLowerInvariant();
                        if (dataPaths.ContainsKey(dataPath) == false)
                        {
                            dataPaths[dataPath] = new List <string>();
                        }

                        if (dataPaths[dataPath].Contains(entryPath) == false)
                        {
                            dataPaths[dataPath].Add(entryPath);
                        }

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

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

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

                        using (var output = File.Create(entryPath))
                        {
                            input.Seek(entry.Offset, SeekOrigin.Begin);

                            if (entry.ShouldCompress == false)
                            {
                                output.WriteFromStream(input, entry.CompressedSize);
                            }
                            else
                            {
                                var bytes = input.ReadBytes(entry.CompressedSize);

                                if (noCrypto == false)
                                {
                                    var crypto = new ArchiveCrypto();
                                    crypto.Decrypt(bytes, 0, bytes.Length);
                                }

                                using (var temp = new MemoryStream(bytes, false))
                                {
                                    var zlib = new InflaterInputStream(temp, new Inflater(true));
                                    output.WriteFromStream(zlib, entry.UncompressedSize);
                                }
                            }
                        }
                    }
                }
            }
        }
        public static void Main(string[] args)
        {
            bool         verbose       = false;
            bool         showHelp      = false;
            uint         baseRevision  = 0;
            uint         revision      = 0;
            string       deletionsPath = null;
            bool         noCrypto      = false;
            const Endian endian        = Endian.Little;

            var options = new OptionSet()
            {
                {
                    "R|baseRevision=", "specify archive base revision",
                    v => baseRevision = v == null ? 0 : uint.Parse(v)
                },
                { "r|revision=", "specify archive revision", v => revision = v == null ? 0 : uint.Parse(v) },
                { "d|deletions=", "path of deletions file", v => deletionsPath = v },
                { "no-crypto", "don't use any encryption", v => noCrypto = v != null },
                { "v|verbose", "show verbose messages", 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_ipf input_directory+", GetExecutableName());
                Console.WriteLine();
                Console.WriteLine("Pack files from input directories into a archive.");
                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], ".ipf");
            }
            else
            {
                outputPath = Path.ChangeExtension(extras[0], ".ipf");
                inputPaths.AddRange(extras.Skip(1));
            }

            var pendingEntries = new SortedDictionary <string, 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))
                {
                    string fullPath = Path.GetFullPath(path);

                    string partPath = fullPath.Substring(inputPath.Length + 1)
                                      .Replace(Path.DirectorySeparatorChar, '/')
                                      .Replace(Path.AltDirectorySeparatorChar, '/');

                    var key = partPath.ToLowerInvariant();

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

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

                        continue;
                    }

                    var archiveSeparatorIndex = partPath.IndexOf('/');
                    if (archiveSeparatorIndex < 0)
                    {
                        continue;
                    }

                    var archiveName = partPath.Substring(0, archiveSeparatorIndex);
                    var fileName    = partPath.Substring(archiveSeparatorIndex + 1);

                    pendingEntries[key] = new PendingEntry(fullPath, archiveName, fileName);
                }
            }

            using (var output = File.Create(outputPath))
            {
                var fileEntries     = new List <ArchiveFileEntry>();
                var deletionEntries = new List <ArchiveDeletionEntry>();

                if (string.IsNullOrEmpty(deletionsPath) == false)
                {
                    if (verbose == true)
                    {
                        Console.WriteLine("Reading deletions...");
                    }

                    var serializer = JsonSerializer.Create();
                    using (var input = File.OpenRead(deletionsPath))
                        using (var streamReader = new StreamReader(input))
                            using (var jsonReader = new JsonTextReader(streamReader))
                            {
                                var jsonDeletionEntries = serializer.Deserialize <JsonArchiveDeletionEntry[]>(jsonReader);
                                deletionEntries.AddRange(jsonDeletionEntries.Select(jde => new ArchiveDeletionEntry()
                                {
                                    Name    = jde.Name,
                                    Archive = jde.Archive,
                                }));
                            }
                }

                if (verbose == true)
                {
                    Console.WriteLine("Writing file data...");
                }

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

                foreach (var pendingEntry in pendingEntries.Select(kv => kv.Value))
                {
                    var fullPath    = pendingEntry.FullPath;
                    var archiveName = pendingEntry.ArchiveName;
                    var fileName    = pendingEntry.FileName;

                    current++;

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

                    var bytes = File.ReadAllBytes(fullPath);

                    var fileEntry = new ArchiveFileEntry();
                    fileEntry.Name             = fileName;
                    fileEntry.Archive          = archiveName;
                    fileEntry.Hash             = CRC32.Compute(bytes, 0, bytes.Length);
                    fileEntry.UncompressedSize = (uint)bytes.Length;
                    fileEntry.Offset           = (uint)output.Position;

                    if (fileEntry.ShouldCompress == true)
                    {
                        int compressionLevel = Deflater.BEST_COMPRESSION;

                        byte[] compressedBytes;

                        using (var temp = new MemoryStream())
                        {
                            var zlib = new DeflaterOutputStream(temp, new Deflater(compressionLevel, true));
                            zlib.WriteBytes(bytes);
                            zlib.Finish();
                            temp.Flush();
                            temp.Position = 0;

                            compressedBytes = temp.ToArray();
                        }

                        if (noCrypto == false)
                        {
                            var crypto = new ArchiveCrypto();
                            crypto.Encrypt(compressedBytes, 0, compressedBytes.Length);
                        }

                        output.WriteBytes(compressedBytes);

                        fileEntry.CompressedSize = (uint)compressedBytes.Length;
                    }
                    else
                    {
                        fileEntry.CompressedSize = fileEntry.UncompressedSize;
                        output.WriteBytes(bytes);
                    }

                    fileEntries.Add(fileEntry);
                }

                if (verbose == true)
                {
                    Console.WriteLine("Writing file table...");
                }

                long fileTableOffset = output.Position;
                for (int i = 0; i < fileEntries.Count; i++)
                {
                    fileEntries[i].Write(output, endian);
                }

                if (verbose == true)
                {
                    Console.WriteLine("Writing deletion table...");
                }

                long deletionTableOffset = output.Position;
                for (int i = 0; i < deletionEntries.Count; i++)
                {
                    deletionEntries[i].Write(output, endian);
                }

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

                ArchiveHeader header;
                header.FileTableCount      = (ushort)fileEntries.Count;
                header.FileTableOffset     = (uint)fileTableOffset;
                header.DeletionTableCount  = (ushort)deletionEntries.Count;
                header.DeletionTableOffset = (uint)deletionTableOffset;
                header.Magic        = ArchiveHeader.Signature;
                header.BaseRevision = baseRevision;
                header.Revision     = revision;
                header.Write(output, endian);

                if (verbose == true)
                {
                    Console.WriteLine("Done!");
                }
            }
        }