public string GetEntryPath(FileEntryKey key, string prefix) { string entryPath = prefix; StringBTree nameBTree = new StringBTree(TableOfContents.Data, (int)TableOfContents.NameTreeOffset); if (nameBTree.TryFindIndex(key.NameIndex, out StringKey nameKey)) { entryPath += nameKey.Value; } if (key.Flags.HasFlag(EntryKeyFlags.File)) { // If it's a file, find the extension aswell StringBTree extBTree = new StringBTree(TableOfContents.Data, (int)TableOfContents.FileExtensionTreeOffset); if (extBTree.TryFindIndex(key.FileExtensionIndex, out StringKey extKey) && !string.IsNullOrEmpty(extKey.Value)) { entryPath += extKey.Value; } } else if (key.Flags.HasFlag(EntryKeyFlags.Directory)) { entryPath += '/'; } lastEntryPath = entryPath; return(entryPath); }
/// <summary> /// Registers a file (not a path). /// </summary> /// <param name="entryIndex"></param> /// <param name="name"></param> /// <param name="extension"></param> public void RegisterFile(uint entryIndex, string name, string extension) { uint nameIndex = RegisterFilename(name); uint extIndex = RegisterExtension(extension); // No extension is also an entry var newEntry = new FileEntryKey(); if (!string.IsNullOrEmpty(extension)) { newEntry.Flags = EntryKeyFlags.File; } newEntry.NameIndex = nameIndex; newEntry.FileExtensionIndex = extIndex; newEntry.EntryIndex = FileInfos.Entries[^ 1].FileIndex;
public void UnpackFromKey(FileEntryKey entryKey) { string entryPath = _volume.GetEntryPath(entryKey, ParentDirectory); if (string.IsNullOrEmpty(entryPath)) { Program.Log($"Could not determine entry path for Entry key at name Index {entryKey.NameIndex}"); return; } string fullEntryPath = Path.Combine(OutDir ?? string.Empty, entryPath); if (entryKey.Flags.HasFlag(EntryKeyFlags.Directory)) { if (_fileIndexesToExtract.Count == 0) // Make sure not to spam when not needed { if (!_volume.IsPatchVolume || _volume.NoUnpack) { Program.Log($"[:] Entering Directory: {entryPath}"); } } var childEntryBTree = new FileEntryBTree(_volume.TableOfContents.Data, (int)_volume.TableOfContents.RootAndFolderOffsets[(int)entryKey.EntryIndex]); var childUnpacker = new EntryUnpacker(_volume, OutDir, entryPath, _fileIndexesToExtract); childEntryBTree.TraverseAndUnpack(childUnpacker); } else { if (_fileIndexesToExtract.Count != 0 && !_fileIndexesToExtract.Contains((int)entryKey.EntryIndex)) { return; } if (!_volume.IsPatchVolume || _volume.NoUnpack) { Program.Log($"[:] Extracting: {entryPath}"); } var nodeBTree = new FileInfoBTree(_volume.TableOfContents.Data, (int)_volume.TableOfContents.NodeTreeOffset); var nodeKey = new FileInfoKey(entryKey.EntryIndex); uint nodeIndex = nodeBTree.SearchIndexByKey(nodeKey); if (nodeIndex != FileInfoKey.InvalidIndex) { _volume.UnpackNode(nodeKey, fullEntryPath); } } }
/// <summary> /// Modifies a current key as a new entry. /// </summary> /// <param name="infoKey">Entry to modify.</param> /// <param name="newEntryPath">Path for the entry.</param> /// <returns></returns> private FileInfoKey ModifyExistingEntryAsNew(FileInfoKey infoKey, string newEntryPath) { // Check paths string[] pathParts = newEntryPath.Split(Path.AltDirectorySeparatorChar); FileEntryBTree currentSubTree = Files[0]; uint newKeyIndex = NextEntryIndex(); // Find the entry key and update it for (int i = 0; i < pathParts.Length; i++) { if (i != pathParts.Length - 1) { // Check actual folders int keyIndex = FileNames.GetIndexOfString(pathParts[i]); if (keyIndex == -1) { throw new ArgumentNullException($"Entry Key for file info key ({infoKey}) has missing file name key: {pathParts[i]}"); } FileEntryKey subTreeKey = currentSubTree.GetFolderEntryByNameIndex((uint)keyIndex); if (subTreeKey is null) { throw new InvalidOperationException($"Tried to modify existing key {newEntryPath} (str index: {keyIndex}), but missing in entries"); } else if (!subTreeKey.Flags.HasFlag(EntryKeyFlags.Directory)) { throw new InvalidOperationException($"Tried to modify existing key {newEntryPath} but entry key ({subTreeKey}) is not marked as directory. Is the volume corrupted?"); } currentSubTree = Files[(int)subTreeKey.EntryIndex]; } else { // Got the location for the subtree // Get our actual file entry key FileEntryKey entryKey = currentSubTree.Entries.FirstOrDefault(e => e.EntryIndex == infoKey.FileIndex); if (entryKey is null) { throw new ArgumentNullException($"Entry Key for file info key ({infoKey}) is missing while modifying."); } // Update it actually entryKey.EntryIndex = newKeyIndex; } } // Find the original entry key, copy from it, add to the tree foreach (FileEntryBTree tree in Files) { foreach (FileEntryKey child in tree.Entries) { if (child.EntryIndex == infoKey.FileIndex) // If the entry key exists, add it { var fileInfo = new FileInfoKey(newKeyIndex); fileInfo.CompressedSize = infoKey.CompressedSize; fileInfo.UncompressedSize = infoKey.UncompressedSize; fileInfo.SegmentIndex = NextSegmentIndex(); // Pushed to the end, so technically the segment is new, will be readjusted at the end anyway fileInfo.Flags = infoKey.Flags; FileInfos.Entries.Add(fileInfo); return(fileInfo); } } } // If it wasn't found, then we already have it infoKey.FileIndex = newKeyIndex; // Move it to the last FileInfos.Entries.Remove(infoKey); FileInfos.Entries.Add(infoKey); return(infoKey); }
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); } }