/// <summary> /// Pack all provided files and edit the table of contents accordingly. /// </summary> /// <param name="FilesToPack">Files to pack.</param> /// <param name="outputDir">Main output dir to use to expose the packed files.</param> public PackCache PackFilesForPatchFileSystem(Dictionary <string, InputPackEntry> FilesToPack, PackCache packCache, string[] filesToRemove, string outputDir, bool packAllAsNewEntries) { // If we are packing as new, ensure the TOC is before all the files (that will come after it) if (packAllAsNewEntries) { ParentHeader.TOCEntryIndex = NextEntryIndex(); } if (filesToRemove.Length > 0) { RemoveFilesFromTOC(filesToRemove); } var newCache = new PackCache(); if (FilesToPack.Count > 0) { // Pick up files we're going to add if there's any PreRegisterNewFilesToPack(FilesToPack); Dictionary <string, FileEntryKey> tocFiles = GetAllRegisteredFileMap(); // Pack Non-Added files first foreach (var tocFile in tocFiles) { if (FilesToPack.TryGetValue(tocFile.Key, out InputPackEntry file) && !file.IsAddedFile) { PackFile(packCache, outputDir, packAllAsNewEntries, newCache, tocFile.Value, file); } } // Pack then added files foreach (var addedFile in FilesToPack.Where(e => e.Value.IsAddedFile)) { var tocFile = tocFiles[addedFile.Value.VolumeDirPath]; PackFile(packCache, outputDir, packAllAsNewEntries, newCache, tocFile, addedFile.Value); } } return(newCache); }
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 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); }