private static void WriteManifest(string path, FileTableManifest manifest) { string content; using (var stringWriter = new StringWriter()) using (var writer = new JsonTextWriter(stringWriter)) { writer.Indentation = 2; writer.IndentChar = ' '; writer.Formatting = Formatting.Indented; var serializer = new JsonSerializer(); serializer.Serialize(writer, manifest); writer.Flush(); stringWriter.Flush(); content = stringWriter.ToString(); } File.WriteAllText(path, content, Encoding.UTF8); }
public static void Main(string[] args) { bool unpackNestedPacks = true; bool verbose = false; bool showHelp = false; var options = new OptionSet() { { "d|dont-unpack-nested-packs", "don't unpack nested .pack files", v => unpackNestedPacks = v == null }, { "v|verbose", "be verbose", v => verbose = v != null }, { "h|help", "show this message and exit", v => showHelp = v != null }, }; List <string> extra; try { extra = 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 (extra.Count < 1 || extra.Count > 2 || showHelp == true) { Console.WriteLine("Usage: {0} [OPTIONS]+ input_FILETABLE [output_directory]", GetExecutableName()); Console.WriteLine("Unpack specified archive."); Console.WriteLine(); Console.WriteLine("Options:"); options.WriteOptionDescriptions(Console.Out); return; } string inputPath = extra[0]; string outputBasePath = extra.Count > 1 ? extra[1] : Path.ChangeExtension(inputPath, null) + "_unpacked"; FileTableFile table; using (var input = File.OpenRead(inputPath)) { table = new FileTableFile(); table.Deserialize(input); } var inputBasePath = Path.GetDirectoryName(inputPath); // TODO(gibbed): // - generate file index for successful repacking // - better name lookup for name hashes (FNV32) // (don't hardcode the list) var names = new string[] { "MENU_COMMON_PACK", "MENU_TEXTURE_MISC_PACK", "MN_AT_ORGANIZE", "MN_BIRTHDAY", "MN_BT_MAIN", "MN_BT_RESULT", "MN_COMMON", "MN_COMMONWIN", "MN_EVENT", "MN_INPUT", "MN_ITEMICON", "MN_KEY_LAYOUT", "MN_MOVIE", "MN_NETWORK", "MN_OPTION", "MN_ORGANIZE", "MN_SHOP2", "MN_STAFFROLL", "MN_STATUS", "MN_TITLE", "MN_WARRENREPORT", "MN_WORLD", }; var nameHashLookup = names.ToDictionary(v => v.HashFNV32(), v => v); var tableManifestPath = Path.Combine(outputBasePath, "@manifest.json"); var tableManifest = new FileTableManifest() { Endian = table.Endian, TitleId1 = table.TitleId1, TitleId2 = table.TitleId2, Unknown32 = table.Unknown32, ParentalLevel = table.ParentalLevel, InstallDataCryptoKey = table.InstallDataCryptoKey, }; foreach (var directory in table.Directories) { var tableDirectory = new TableDirectory() { Id = directory.Id, BasePath = Path.Combine(outputBasePath, $"{directory.Id}"), }; var fileContainers = new List <IFileContainer>() { tableDirectory, }; var binPath = Path.Combine(inputBasePath, $"{directory.Id:X4}.BIN"); using (var input = File.OpenRead(binPath)) { var fileQueue = new Queue <QueuedFile>(); foreach (var file in directory.Files) { long dataOffset; dataOffset = directory.DataBaseOffset; dataOffset += (file.DataBlockOffset << directory.DataBlockSize) * FileTableFile.BaseDataBlockSize; fileQueue.Enqueue(new QueuedFile() { Id = file.Id, Parent = tableDirectory, NameHash = file.NameHash, DataOffset = dataOffset, DataSize = file.DataSize, }); } while (fileQueue.Count > 0) { var file = fileQueue.Dequeue(); var parent = file.Parent; var nameBuilder = new StringBuilder(); nameBuilder.Append($"{file.Id}"); string name = null; if (file.NameHash != null) { if (nameHashLookup.TryGetValue(file.NameHash.Value, out name) == true) { nameBuilder.Append($"_{name}"); } else { nameBuilder.Append($"_HASH[{file.NameHash.Value:X8}]"); } } if (parent.IdCounts != null) { var idCounts = parent.IdCounts; int idCount; idCounts.TryGetValue(file.Id, out idCount); idCount++; idCounts[file.Id] = idCount; if (idCount > 1) { nameBuilder.Append($"_DUP_{idCount}"); } } if (unpackNestedPacks == true && file.DataSize >= 8) { input.Position = file.DataOffset; var fileMagic = input.ReadValueU32(Endian.Little); if (fileMagic == PackFile.Signature || fileMagic.Swap() == PackFile.Signature) { input.Position = file.DataOffset; var nestedPack = HandleNestedPack(input, fileQueue, file.Id, nameBuilder.ToString(), parent); fileContainers.Add(nestedPack); parent.FileManifests.Add(new FileTableManifest.File() { Id = file.Id, NameHash = file.NameHash, Name = name, IsPack = true, PackId = PackId.Create(file.PackRawId), Path = CleanPathForManifest(PathHelper.GetRelativePath(parent.BasePath, nestedPack.ManifestPath)), }); continue; } } var outputPath = Path.Combine(parent.BasePath, nameBuilder.ToString()); var outputParentPath = Path.GetDirectoryName(outputPath); if (string.IsNullOrEmpty(outputParentPath) == false) { Directory.CreateDirectory(outputParentPath); } input.Position = file.DataOffset; var extension = FileDetection.Guess(input, (int)file.DataSize, file.DataSize); outputPath = Path.ChangeExtension(outputPath, extension); if (verbose == true) { Console.WriteLine(outputPath); } input.Position = file.DataOffset; using (var output = File.Create(outputPath)) { output.WriteFromStream(input, file.DataSize); } parent.FileManifests.Add(new FileTableManifest.File() { Id = file.Id, NameHash = file.NameHash, Name = name, PackId = PackId.Create(file.PackRawId), Path = CleanPathForManifest(PathHelper.GetRelativePath(parent.BasePath, outputPath)), }); } } foreach (var fileContainer in fileContainers) { WriteManifest(fileContainer.ManifestPath, fileContainer); } tableManifest.Directories.Add(new FileTableManifest.Directory() { Id = directory.Id, DataBlockSize = directory.DataBlockSize, IsInInstallData = directory.IsInInstallData, FileManifest = CleanPathForManifest( PathHelper.GetRelativePath(outputBasePath, tableDirectory.ManifestPath)), }); } WriteManifest(tableManifestPath, tableManifest); }