private bool DecryptTOC() { if (VolumeHeader is null) { throw new InvalidOperationException("Header was not yet loaded"); } if (IsPatchVolume) { string path = PDIPFSPathResolver.GetPathFromSeed(VolumeHeader.TOCEntryIndex); string localPath = Path.Combine(this.PatchVolumeFolder, path); Program.Log($"[!] Volume Patch Path Table of contents located at: {localPath}", true); if (!File.Exists(localPath)) { Program.Log($"[X] Error: Unable to locate PDIPFS main TOC file on local filesystem. ({path})", true); return(false); } using var fs = new FileStream(localPath, FileMode.Open); byte[] data = new byte[VolumeHeader.CompressedTOCSize]; fs.Read(data); // Accessing a new file, we need to decrypt the header again Program.Log($"[-] TOC Entry is {VolumeHeader.TOCEntryIndex} which is at {path} - decrypting it", true); Keyset.CryptData(data, VolumeHeader.TOCEntryIndex); Program.Log($"[-] Decompressing TOC file..", true); if (!MiscUtils.TryInflateInMemory(data, VolumeHeader.TOCSize, out byte[] deflatedData))
public static void List(ListVerbs options) { bool isFile = File.Exists(options.InputPath); bool isDir = false; if (!isFile) { isDir = Directory.Exists(options.InputPath); if (!isDir) { Console.WriteLine($"[X] Volume file or PDIPFS folder \"{options.InputPath}\" does not exist."); return; } } Keyset[] keyset = CheckKeys(); if (keyset is null) { return; } bool found = false; GTVolume vol = null; foreach (var k in keyset) { vol = GTVolume.Load(k, options.InputPath, isDir, Syroot.BinaryData.Core.Endian.Big); if (vol != null) { found = true; break; } } if (!found) { Console.WriteLine($"[X] Could not unpack volume. Make sure that you have a valid game key/seed in your key.json."); return; } using var sw = new StreamWriter(options.OutputPath); var entries = vol.TableOfContents.GetAllRegisteredFileMap(); foreach (var entry in entries) { if (vol.IsPatchVolume) { sw.WriteLine($"{entry.Key} ({entry.Value.EntryIndex}) - {PDIPFSPathResolver.GetPathFromSeed(entry.Value.EntryIndex)}"); } else { sw.WriteLine($"{entry.Key} ({entry.Value.EntryIndex})"); } } sw.WriteLine($"[!] Wrote {entries.Count} at {options.OutputPath}."); }
/// <summary> /// Saves the table of contents to a Patch File System file. /// </summary> /// <param name="outputDir"></param> /// <param name="compressedTocSize"></param> /// <param name="uncompressedSize"></param> public void SaveToPatchFileSystem(string outputDir, out uint compressedTocSize, out uint uncompressedSize) { byte[] tocSerialized = Serialize(); byte[] compressedToc = MiscUtils.ZlibCompress(tocSerialized); uncompressedSize = (uint)tocSerialized.Length; compressedTocSize = (uint)compressedToc.Length; ParentVolume.Keyset.CryptData(compressedToc, ParentHeader.TOCEntryIndex); string path = Path.Combine(outputDir, PDIPFSPathResolver.GetPathFromSeed(ParentHeader.TOCEntryIndex)); Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllBytes(path, compressedToc); }
private void PackFile(PackCache packCache, string outputDir, bool packAllAsNewEntries, PackCache newCache, FileEntryKey tocFile, InputPackEntry file) { Program.Log($"[:] Pack: Processing {file.VolumeDirPath}"); FileInfoKey key = FileInfos.GetByFileIndex(tocFile.EntryIndex); if (packAllAsNewEntries && !file.IsAddedFile) { uint oldEntryFileIndex = key.FileIndex; key = ModifyExistingEntryAsNew(key, file.VolumeDirPath); Program.Log($"[:] Entry key for {file.VolumeDirPath} changed as new: {oldEntryFileIndex} -> {key.FileIndex}"); } uint newUncompressedSize = (uint)file.FileSize; uint newCompressedSize = (uint)file.FileSize; string pfsFilePath = PDIPFSPathResolver.GetPathFromSeed(tocFile.EntryIndex); // Check for cached file if (ParentVolume.UsePackingCache && packCache.HasValidCachedEntry(file, key.FileIndex, out PackedCacheEntry validCacheEntry)) { string oldFilePath = Path.Combine(outputDir, pfsFilePath); if (File.Exists(oldFilePath)) { newCache.Entries.Add(file.VolumeDirPath, validCacheEntry); Program.Log($"[:] Pack: {file.VolumeDirPath} found in cache file, does not need compressing/encrypting"); string movePath = Path.Combine($"{outputDir}_temp", pfsFilePath); Directory.CreateDirectory(Path.GetDirectoryName(movePath)); File.Move(oldFilePath, Path.Combine($"{outputDir}_temp", pfsFilePath)); UpdateKeyAndRetroactiveAdjustSegments(key, (uint)validCacheEntry.CompressedFileSize, (uint)validCacheEntry.FileSize); return; } else { Program.Log($"[:] Pack: {file.VolumeDirPath} found in cache file but actual file is missing ({pfsFilePath}) - recreating it"); } } byte[] fileData = File.ReadAllBytes(file.FullPath); if (ParentVolume.NoCompress) { key.Flags &= ~FileInfoFlags.Compressed; } else if (key.Flags.HasFlag(FileInfoFlags.Compressed)) { Program.Log($"[:] Pack: Compressing {file.VolumeDirPath}"); fileData = MiscUtils.ZlibCompress(fileData); newCompressedSize = (uint)fileData.Length; } Program.Log($"[:] Pack: Saving and encrypting {file.VolumeDirPath} -> {pfsFilePath}"); // Will also update the ones we pre-registered UpdateKeyAndRetroactiveAdjustSegments(key, newCompressedSize, newUncompressedSize); ParentVolume.Keyset.CryptBytes(fileData, fileData, key.FileIndex); string outputFile = Path.Combine($"{outputDir}_temp", pfsFilePath); Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); File.WriteAllBytes(outputFile, fileData); if (ParentVolume.UsePackingCache) { // Add to our new cache var newCacheEntry = new PackedCacheEntry() { FileIndex = tocFile.EntryIndex, FileSize = newUncompressedSize, LastModified = file.LastModified, VolumePath = file.VolumeDirPath, CompressedFileSize = newCompressedSize, }; newCache.Entries.Add(file.VolumeDirPath, newCacheEntry); } }
public bool UnpackNode(FileInfoKey nodeKey, string filePath) { ulong offset = DataOffset + (ulong)nodeKey.SegmentIndex * GTVolumeTOC.SEGMENT_SIZE; uint uncompressedSize = nodeKey.UncompressedSize; if (!IsPatchVolume) { if (NoUnpack) { return(false); } Stream.Position = (long)offset; if (nodeKey.Flags.HasFlag(FileInfoFlags.Compressed)) { if (!MiscUtils.DecryptCheckCompression(Stream, Keyset, nodeKey.FileIndex, uncompressedSize)) { Program.Log($"[X] Failed to decompress file ({filePath}) while unpacking file info key {nodeKey.FileIndex}", forceConsolePrint: true); return(false); } Directory.CreateDirectory(Path.GetDirectoryName(filePath)); Stream.Position -= 8; MiscUtils.DecryptAndInflateToFile(Keyset, Stream, nodeKey.FileIndex, uncompressedSize, filePath, false); } else { Directory.CreateDirectory(Path.GetDirectoryName(filePath)); MiscUtils.DecryptToFile(Keyset, Stream, nodeKey.FileIndex, uncompressedSize, filePath, false); } } else { string patchFilePath = PDIPFSPathResolver.GetPathFromSeed(nodeKey.FileIndex); string localPath = this.PatchVolumeFolder + "/" + patchFilePath; if (NoUnpack) { return(false); } /* I'm really not sure if there's a better way to do this. * Volume files, at least nodes don't seem to even store any special flag whether * it is located within an actual volume file or a patch volume. The only thing that is different is the sector index.. Sometimes node index when it's updated * It's slow, but somewhat works I guess.. * */ if (!File.Exists(localPath)) { return(false); } Program.Log($"[:] Unpacking: {patchFilePath} -> {filePath}"); using var fs = new FileStream(localPath, FileMode.Open); if (fs.Length >= 7) { Span <byte> magic = stackalloc byte[6]; fs.Read(magic); if (Encoding.ASCII.GetString(magic).StartsWith("BSDIFF")) { Program.Log($"[X] Detected BSDIFF file for {filePath} ({patchFilePath}), can not unpack yet. (fileID {nodeKey.FileIndex})", forceConsolePrint: true); return(false); } fs.Position = 0; } if (nodeKey.Flags.HasFlag(FileInfoFlags.Compressed)) { if (!MiscUtils.DecryptCheckCompression(fs, Keyset, nodeKey.FileIndex, uncompressedSize)) { Program.Log($"[X] Failed to decompress file {filePath} ({patchFilePath}) while unpacking file info key {nodeKey.FileIndex}", forceConsolePrint: true); return(false); } Directory.CreateDirectory(Path.GetDirectoryName(filePath)); fs.Position = 0; MiscUtils.DecryptAndInflateToFile(Keyset, fs, nodeKey.FileIndex, filePath); } else { Directory.CreateDirectory(Path.GetDirectoryName(filePath)); MiscUtils.DecryptToFile(Keyset, fs, nodeKey.FileIndex, filePath); } } return(true); }
public void PackFiles(string outrepackDir, string[] filesToRemove, bool packAllAsNew, string customTitleID) { if (FilesToPack.Count == 0 && filesToRemove.Length == 0) { Program.Log("[X] Found no files to pack or remove from volume.", forceConsolePrint: true); Console.WriteLine("[?] Continue? (Y/N)"); if (Console.ReadKey().Key != ConsoleKey.Y) { return; } } // Leftover? if (Directory.Exists($"{outrepackDir}_temp")) { Directory.Delete($"{outrepackDir}_temp", true); } // Create temp to make sure we aren't transfering user leftovers Directory.CreateDirectory($"{outrepackDir}_temp"); Program.Log($"[-] Preparing to pack {FilesToPack.Count} files, and remove {filesToRemove.Length} files"); PackCache newCache = TableOfContents.PackFilesForPatchFileSystem(FilesToPack, _packCache, filesToRemove, outrepackDir, packAllAsNew); if (UsePackingCache) { newCache.Save(".pack_cache"); } // Delete main one if needed if (Directory.Exists(outrepackDir)) { Directory.Delete(outrepackDir, true); } Directory.Move($"{outrepackDir}_temp", outrepackDir); Program.Log($"[-] Verifying and fixing Table of Contents segment sizes if needed"); if (!TableOfContents.TryCheckAndFixInvalidSegmentIndexes()) { Program.Log($"[-] Re-ordered segment indexes."); } else { Program.Log($"[/] Segment sizes are correct."); } if (packAllAsNew) { Program.Log($"[-] Packing as new: New TOC Entry Index is {VolumeHeader.TOCEntryIndex}."); } Program.Log($"[-] Saving Table of Contents ({PDIPFSPathResolver.GetPathFromSeed(VolumeHeader.TOCEntryIndex)})"); TableOfContents.SaveToPatchFileSystem(outrepackDir, out uint compressedSize, out uint uncompressedSize); if (!string.IsNullOrEmpty(customTitleID) && customTitleID.Length <= 128) { VolumeHeader.HasCustomGameID = true; VolumeHeader.TitleID = customTitleID; } VolumeHeader.CompressedTOCSize = compressedSize; VolumeHeader.TOCSize = uncompressedSize; VolumeHeader.TotalVolumeSize = TableOfContents.GetTotalPatchFileSystemSize(compressedSize); Program.Log($"[-] Saving main volume header ({PDIPFSPathResolver.Default})"); byte[] header = VolumeHeader.Serialize(); Span <uint> headerBlocks = MemoryMarshal.Cast <byte, uint>(header); Keyset.EncryptBlocks(headerBlocks, headerBlocks); Keyset.CryptData(header, BASE_VOLUME_ENTRY_INDEX); string headerPath = Path.Combine(outrepackDir, PDIPFSPathResolver.Default); Directory.CreateDirectory(Path.GetDirectoryName(headerPath)); File.WriteAllBytes(headerPath, header); Program.Log($"[/] Done packing.", forceConsolePrint: true); }