public static void Main(string[] args) { bool showHelp = false; string currentProject = null; var options = new OptionSet() { { "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 != 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 = Manager.Load(currentProject); if (manager.ActiveProject == null) { Console.WriteLine("Nothing to do: no active project loaded."); return; } var project = manager.ActiveProject; var version = -1; HashList <ulong> knownHashes = null; HashList <ulong> subFatHashes = null; 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 fatPaths = new List <string>(); fatPaths.AddRange(Directory.GetFiles(installPath, "*.fat", SearchOption.AllDirectories)); var outputPaths = new List <string>(); var breakdown = new Breakdown(); var tracking = new Tracking(); Console.WriteLine("Processing..."); for (int i = 0; i < fatPaths.Count; i++) { var fatPath = fatPaths[i]; var datPath = Path.ChangeExtension(fatPath, ".dat"); var outputPath = GetListPath(installPath, fatPath); 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); if (File.Exists(fatPath + ".bak") == true) { fatPath += ".bak"; datPath += ".bak"; } var fat = new BigFile(); using (var input = File.OpenRead(fatPath)) { fat.Deserialize(input); } if (version == -1) { version = fat.Version; knownHashes = manager.LoadListsFileNames(fat.Version); subFatHashes = manager.LoadListsSubFatNames(fat.Version); } else if (version != fat.Version) { throw new InvalidOperationException(); } if (knownHashes == null || subFatHashes == null) { throw new InvalidOperationException(); } HandleEntries(fat.Entries.Select(e => e.NameHash).Distinct(), knownHashes, tracking, breakdown, outputPath); using (var input = File.OpenRead(datPath)) { foreach (var headerEntry in fat.Entries.Where(e => subFatHashes.Contains(e.NameHash) == true)) { var subFat = new SubFatFile(); using (var temp = new MemoryStream()) { Big.EntryDecompression.Decompress(headerEntry, input, temp); temp.Position = 0; subFat.Deserialize(temp, fat); } var matchingSubFats = fat.SubFats .Where(sf => subFat.Entries.SequenceEqual(sf.Entries)) .ToArray(); if (matchingSubFats.Length == 0) { continue; } if (matchingSubFats.Length > 1) { throw new InvalidOperationException(); } var subfatPath = GetListPath(installPath, fatPath, FilterEntryName(subFatHashes[headerEntry.NameHash])); if (subfatPath == null) { throw new InvalidOperationException(); } Console.WriteLine(subfatPath); subfatPath = Path.Combine(listsPath, subfatPath); HandleEntries(subFat.Entries.Select(e => e.NameHash), knownHashes, tracking, breakdown, subfatPath); } } } using (var output = new StreamWriter(Path.Combine(Path.Combine(listsPath, "files"), "status.txt"))) { output.WriteLine("{0}", new Breakdown() { Known = tracking.Names.Distinct().Count(), Total = tracking.Hashes.Distinct().Count(), }); } }
public static void Main(string[] args) { bool showHelp = false; bool extractUnknowns = true; bool extractFiles = true; bool extractSubFats = true; bool unpackSubFats = false; string filterPattern = null; bool overwriteFiles = false; bool verbose = false; var options = new OptionSet() { { "o|overwrite", "overwrite existing files", v => overwriteFiles = v != null }, { "nf|no-files", "don't extract files", v => extractFiles = v == null }, { "nu|no-unknowns", "don't extract unknown files", v => extractUnknowns = v == null }, { "ns|no-subfats", "don't extract subfats", v => extractSubFats = v == null }, { "us|unpack-subfats", "unpack files from subfats", v => unpackSubFats = v != null }, { "f|filter=", "only extract 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 }, }; 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_fat [output_dir]", GetExecutableName()); Console.WriteLine(); Console.WriteLine("Unpack files from a Big File (FAT/DAT pair)."); Console.WriteLine(); Console.WriteLine("Options:"); options.WriteOptionDescriptions(Console.Out); return; } string fatPath = extras[0]; string outputPath = extras.Count > 1 ? extras[1] : Path.ChangeExtension(fatPath, null) + "_unpack"; string datPath; Regex filter = null; if (string.IsNullOrEmpty(filterPattern) == false) { filter = new Regex(filterPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); } if (Path.GetExtension(fatPath) == ".dat") { datPath = fatPath; fatPath = Path.ChangeExtension(fatPath, ".fat"); } else { datPath = Path.ChangeExtension(fatPath, ".dat"); } if (verbose == true) { Console.WriteLine("Loading project..."); } var manager = ProjectData.Manager.Load(); if (manager.ActiveProject == null) { Console.WriteLine("Warning: no active project loaded."); } if (verbose == true) { Console.WriteLine("Reading FAT..."); } BigFile fat; using (var input = File.OpenRead(fatPath)) { fat = new BigFile(); fat.Deserialize(input); } var hashes = manager.LoadListsFileNames(fat.Version); var subFatHashes = manager.LoadListsSubFatNames(fat.Version); using (var input = File.OpenRead(datPath)) { if (extractFiles == true) { Big.Entry[] entries; if (extractSubFats == true && unpackSubFats == true) { entries = fat.Entries.Concat(fat.SubFats.SelectMany(sf => sf.Entries)) .OrderBy(e => e.Offset) .ToArray(); } else { entries = fat.Entries.OrderBy(e => e.Offset).ToArray(); } if (entries.Length > 0) { if (verbose == true) { Console.WriteLine("Unpacking files..."); } long current = 0; long total = entries.Length; var padding = total.ToString(CultureInfo.InvariantCulture).Length; var duplicates = new Dictionary <ulong, int>(); foreach (var entry in entries) { current++; if (subFatHashes.Contains(entry.NameHash) == true) { continue; } string entryName; if (GetEntryName(input, fat, entry, hashes, extractUnknowns, out entryName) == false) { continue; } if (duplicates.ContainsKey(entry.NameHash) == true) { var number = duplicates[entry.NameHash]++; var e = Path.GetExtension(entryName); var nn = Path.ChangeExtension( Path.ChangeExtension(entryName, null) + "__DUPLICATE_" + number.ToString(CultureInfo.InvariantCulture), e); entryName = Path.Combine("__DUPLICATE", nn); } else { duplicates[entry.NameHash] = 0; } if (filter != null && filter.IsMatch(entryName) == false) { continue; } 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); var entryParent = Path.GetDirectoryName(entryPath); if (string.IsNullOrEmpty(entryParent) == false) { Directory.CreateDirectory(entryParent); } using (var output = File.Create(entryPath)) { EntryDecompression.Decompress(entry, input, output); } } } } if (extractSubFats == true && unpackSubFats == false && fat.SubFats.Count > 0) { if (verbose == true) { Console.WriteLine("Unpacking subfats..."); } var subFatsFromFat = fat.SubFats.ToList(); long current = 0; long total = subFatsFromFat.Count; var padding = total.ToString(CultureInfo.InvariantCulture).Length; foreach (var headerEntry in fat.Entries.Where(e => subFatHashes.Contains(e.NameHash) == true)) { current++; var subFat = new SubFatFile(); using (var temp = new MemoryStream()) { EntryDecompression.Decompress(headerEntry, input, temp); temp.Position = 0; subFat.Deserialize(temp, fat); } var matchingSubFats = subFatsFromFat .Where(sf => subFat.Entries.SequenceEqual(sf.Entries)) .ToArray(); if (matchingSubFats.Length == 0) { continue; } if (matchingSubFats.Length > 1) { throw new InvalidOperationException(); } var entryName = subFatHashes[headerEntry.NameHash]; entryName = FilterEntryName(entryName); var entryHeaderPath = Path.Combine(outputPath, "__SUBFAT", entryName); if (overwriteFiles == false && File.Exists(entryHeaderPath) == true) { continue; } if (verbose == true) { Console.WriteLine("[{0}/{1}] {2}", current.ToString(CultureInfo.InvariantCulture).PadLeft(padding), total, entryName); } var entryParent = Path.GetDirectoryName(entryHeaderPath); if (string.IsNullOrEmpty(entryParent) == false) { Directory.CreateDirectory(entryParent); } var entryDataPath = Path.ChangeExtension(entryHeaderPath, ".dat"); var rebuiltFat = new BigFile { Version = fat.Version, Platform = fat.Platform, Unknown74 = fat.Unknown74 }; using (var output = File.Create(entryDataPath)) { var rebuiltEntries = new List <Big.Entry>(); foreach (var entry in subFat.Entries.OrderBy(e => e.Offset)) { var rebuiltEntry = new Big.Entry { NameHash = entry.NameHash, UncompressedSize = entry.UncompressedSize, CompressedSize = entry.CompressedSize, Offset = output.Position, CompressionScheme = entry.CompressionScheme }; input.Seek(entry.Offset, SeekOrigin.Begin); output.WriteFromStream(input, entry.CompressedSize); output.Seek(output.Position.Align(16), SeekOrigin.Begin); rebuiltEntries.Add(rebuiltEntry); } rebuiltFat.Entries.AddRange(rebuiltEntries.OrderBy(e => e.NameHash)); } using (var output = File.Create(entryHeaderPath)) { rebuiltFat.Serialize(output); } foreach (var matchingSubFat in matchingSubFats) { subFatsFromFat.Remove(matchingSubFat); } } if (subFatsFromFat.Count > 0) { Console.WriteLine("Warning: could not identify {0} subfats", subFatsFromFat.Count); } } } }