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