public static void Main(string[] args) { bool showHelp = false; bool? extractUnknowns = null; bool overwriteFiles = false; bool verbose = true; string currentProject = null; string filterPattern = null; 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 ? false : extractUnknowns }, { "ou|only-unknowns", "only extract unknown files", v => extractUnknowns = v != null ? true : extractUnknowns }, { "f|filter=", "filter 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 }, { "p|project=", "override current project", v => currentProject = v }, }; 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 || Is000(extras[0]) == false) { Console.WriteLine("Usage: {0} [OPTIONS]+ input_file.000 [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) + "_unpack"; Regex filter = null; if (string.IsNullOrEmpty(filterPattern) == false) { filter = new Regex(filterPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); } string bigPathSuffix; var bigPathBase = GetBasePath(inputPath, out bigPathSuffix); var manager = ProjectData.Manager.Load(currentProject); if (manager.ActiveProject == null) { Console.WriteLine("Warning: no active project loaded."); } var big = new BigArchiveFileV1 { Endian = manager.GetSetting("archive_endianness", Endian.Little), DataAlignment = manager.GetSetting <uint>("archive_alignment", 0x7FF00000), }; var compressionType = manager.GetSetting("archive_compression_type", CompressionType.None); using (var input = File.OpenRead(inputPath)) { big.Deserialize(input); } if (big.Entries.Any(e => e.CompressedSize != 0) == true && compressionType == CompressionType.None) { throw new InvalidOperationException("compressed entries not supported"); } var hashes = manager.LoadLists("*.filelist", s => s.HashFileName(), s => s.ToLowerInvariant()); Directory.CreateDirectory(outputPath); var settings = new XmlWriterSettings() { Indent = true, }; using (var xml = XmlWriter.Create( Path.Combine(outputPath, "bigfile.xml"), settings)) { xml.WriteStartDocument(); xml.WriteStartElement("files"); xml.WriteAttributeString("endian", big.Endian.ToString().ToLowerInvariant()); xml.WriteAttributeString("alignment", big.DataAlignment.ToString("X8")); Stream data = null; uint? currentBigFile = null; uint? lastLocale = null; var maxBlocksPerFile = big.DataAlignment / 2048; { long current = 0; long total = big.Entries.Count; foreach (var entry in big.Entries.OrderBy(e => e.Offset)) { current++; var entryBigFile = entry.Offset / maxBlocksPerFile; var entryOffset = (entry.Offset % maxBlocksPerFile) * 2048; if (currentBigFile.HasValue == false || currentBigFile.Value != entryBigFile) { if (data != null) { data.Close(); } currentBigFile = entryBigFile; var bigPath = string.Format("{0}.{1}{2}", bigPathBase, currentBigFile.Value.ToString(CultureInfo.InvariantCulture) .PadLeft(3, '0'), bigPathSuffix); if (verbose == true) { Console.WriteLine(bigPath); } data = File.OpenRead(bigPath); } string name = hashes[entry.NameHash]; if (name == null) { if (extractUnknowns.HasValue == true && extractUnknowns.Value == false) { continue; } string extension; // detect type { var guess = new byte[64]; int read = 0; if (entry.UncompressedSize > 0) { if (entry.CompressedSize != 0) { data.Seek(entryOffset, SeekOrigin.Begin); if (compressionType == CompressionType.Zlib) { var zlib = new InflaterInputStream(data); read = zlib.Read(guess, 0, (int)Math.Min( entry.UncompressedSize, guess.Length)); } else { throw new NotSupportedException(); } } else { data.Seek(entryOffset, SeekOrigin.Begin); read = data.Read(guess, 0, (int)Math.Min( entry.UncompressedSize, guess.Length)); } } extension = FileExtensions.Detect( guess, Math.Min(guess.Length, read)); } name = entry.NameHash.ToString("X8"); name = Path.ChangeExtension(name, "." + extension); name = Path.Combine(extension, name); name = Path.Combine("__UNKNOWN", name); } else { if (extractUnknowns.HasValue == true && extractUnknowns.Value == true) { continue; } name = name.Replace("/", "\\"); if (name.StartsWith("\\") == true) { name = name.Substring(1); } } if (entry.Locale == 0xFFFFFFFF) { name = Path.Combine("default", name); } else { name = Path.Combine(entry.Locale.ToString("X8"), name); } if (filter != null && filter.IsMatch(name) == false) { continue; } var entryPath = Path.Combine(outputPath, name); var entryParentPath = Path.GetDirectoryName(entryPath); if (string.IsNullOrEmpty(entryParentPath) == false) { Directory.CreateDirectory(entryParentPath); } if (lastLocale.HasValue == false || lastLocale.Value != entry.Locale) { xml.WriteComment(string.Format(" {0} = {1} ", entry.Locale.ToString("X8"), ((ArchiveLocale)entry.Locale))); lastLocale = entry.Locale; } xml.WriteStartElement("entry"); xml.WriteAttributeString("hash", entry.NameHash.ToString("X8")); xml.WriteAttributeString("locale", entry.Locale.ToString("X8")); xml.WriteValue(name); xml.WriteEndElement(); if (overwriteFiles == false && File.Exists(entryPath) == true) { continue; } if (verbose == true) { Console.WriteLine("[{0}/{1}] {2}", current, total, name); } using (var output = File.Create(entryPath)) { if (entry.UncompressedSize > 0) { if (entry.CompressedSize != 0) { data.Seek(entryOffset, SeekOrigin.Begin); if (compressionType == CompressionType.Zlib) { using (var temp = data.ReadToMemoryStream((int)entry.CompressedSize)) { var zlib = new InflaterInputStream(temp); output.WriteFromStream(zlib, entry.UncompressedSize); } } else { throw new NotSupportedException(); } } else { data.Seek(entryOffset, SeekOrigin.Begin); output.WriteFromStream(data, entry.UncompressedSize); } } } } } if (data != null) { data.Close(); } xml.WriteEndElement(); xml.WriteEndDocument(); xml.Flush(); } }
public static void Main(string[] args) { bool showHelp = false; string currentProject = null; var endian = Endian.Little; var options = new OptionSet() { { "h|help", "show this message and exit", v => showHelp = v != null }, { "l|little-endian", "operate in little-endian mode", v => endian = v != null ? Endian.Little : endian }, { "b|big-endian", "operate in big-endian mode", v => endian = v != null ? Endian.Big : endian }, { "p|project=", "override current project", v => currentProject = v }, }; 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 != 0 || showHelp == true) { Console.WriteLine("Usage: {0} [OPTIONS]+", GetExecutableName()); Console.WriteLine(); Console.WriteLine("Options:"); options.WriteOptionDescriptions(Console.Out); return; } Console.WriteLine("Loading project..."); var manager = ProjectData.Manager.Load(currentProject); if (manager.ActiveProject == null) { Console.WriteLine("Nothing to do: no active project loaded."); return; } var project = manager.ActiveProject; var hashes = manager.LoadLists("*.filelist", s => s.HashFileName(), s => s.ToLowerInvariant()); var installPath = project.InstallPath; var listsPath = project.ListsPath; if (installPath == null) { Console.WriteLine("Could not detect install path."); return; } if (listsPath == null) { Console.WriteLine("Could not detect lists path."); return; } Console.WriteLine("Searching for archives..."); var inputPaths = new List <string>(); inputPaths.AddRange(Directory.GetFiles(installPath, "*.000", SearchOption.AllDirectories)); var outputPaths = new List <string>(); var fileAlignment = manager.GetSetting <uint>("archive_alignment", 0x7FF00000); Console.WriteLine("Processing..."); foreach (var inputPath in inputPaths) { var outputPath = GetListPath(installPath, inputPath); if (outputPath == null) { throw new InvalidOperationException(); } Console.WriteLine(outputPath); outputPath = Path.Combine(listsPath, outputPath); if (outputPaths.Contains(outputPath) == true) { throw new InvalidOperationException(); } outputPaths.Add(outputPath); var big = new BigArchiveFileV1() { Endian = endian, DataAlignment = fileAlignment, }; if (File.Exists(inputPath + ".bak") == true) { using (var input = File.OpenRead(inputPath + ".bak")) { big.Deserialize(input); } } else { using (var input = File.OpenRead(inputPath)) { big.Deserialize(input); } } var localBreakdown = new Breakdown(); var names = new List <string>(); foreach (var nameHash in big.Entries.Select(e => e.NameHash).Distinct()) { var name = hashes[nameHash]; if (name != null) { if (names.Contains(name) == false) { names.Add(name); localBreakdown.Known++; } } localBreakdown.Total++; } names.Sort(); var outputParentPath = Path.GetDirectoryName(outputPath); if (string.IsNullOrEmpty(outputParentPath) == false) { Directory.CreateDirectory(outputParentPath); } using (var output = new StreamWriter(outputPath)) { output.WriteLine("; {0}", localBreakdown); foreach (string name in names) { output.WriteLine(name); } } } }