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); } } } } } }
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!"); } } }