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); }
public static void Main(string[] args) { bool showHelp = false; var options = new OptionSet() { { "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 < 3 || extra.Count > 3 || showHelp == true || ParseArgument(extra[1], out uint directoryId) == false || ParseArgument(extra[2], out long offset) == false) { Console.WriteLine("Usage: {0} [OPTIONS]+ input_FILETABLE <dir> <offset>", GetExecutableName()); Console.WriteLine(); Console.WriteLine("Options:"); options.WriteOptionDescriptions(Console.Out); return; } string inputPath = extra[0]; FileTableFile table; using (var input = File.OpenRead(inputPath)) { table = new FileTableFile(); table.Deserialize(input); } var directoryIndex = table.Directories.FindIndex(de => de.Id == directoryId); if (directoryIndex < 0) { Console.WriteLine($"Directory ID {directoryId} not found."); return; } var directory = table.Directories[directoryIndex]; var blockSize = FileTableFile.BaseDataBlockSize << directory.DataBlockSize; var fileIndex = directory.Files.FindIndex( fe => offset >= fe.DataBlockOffset * blockSize && offset < (fe.DataBlockOffset * blockSize) + fe.DataSize); if (fileIndex < 0) { Console.WriteLine($"Offset does not appear to correspond to file within directory ID {directoryId}."); return; } var inputBasePath = Path.GetDirectoryName(inputPath); var binPath = Path.Combine(inputBasePath, $"{directory.Id:X4}.BIN"); var file = directory.Files[fileIndex]; Console.WriteLine($"Directory ID: {directory.Id}"); Console.WriteLine($" File ID: {file.Id}"); if (file.DataSize < 8) { return; } long dataPosition; int level = 2; using (var input = File.OpenRead(binPath)) { dataPosition = file.DataBlockOffset * blockSize; while (true) { input.Position = dataPosition; var fileMagic = input.ReadValueU32(Endian.Little); if (fileMagic != PackFile.Signature && fileMagic.Swap() != PackFile.Signature) { break; } input.Position = dataPosition; var packFile = new PackFile(); packFile.Deserialize(input); int i; long foundPosition = -1; for (i = 0; i < packFile.Entries.Count; i++) { var entryOffset = packFile.Entries[i].Offset; var nextEntryOffset = i + 1 < packFile.Entries.Count ? packFile.Entries[i + 1].Offset : packFile.TotalSize; var startPosition = dataPosition + entryOffset; var endPosition = dataPosition + nextEntryOffset; if (offset >= startPosition && offset < endPosition) { foundPosition = startPosition; break; } } if (foundPosition < 0) { break; } Console.WriteLine($"{"".PadLeft(level)}Pack file index: {i}"); dataPosition = foundPosition; level++; } } var relativePosition = offset - dataPosition; Console.WriteLine($"{"".PadLeft(level)}Offset: 0x{relativePosition:X} ({relativePosition})"); }