/// <summary> /// Unpacks all the files within the volume. /// </summary> public void UnpackFiles(IEnumerable <int> fileIndexesToExtract) { FileEntryBTree rootEntries = new FileEntryBTree(this.TableOfContents.Data, (int)TableOfContents.RootAndFolderOffsets[0]); var unpacker = new EntryUnpacker(this, OutputDirectory, "", fileIndexesToExtract.ToList()); rootEntries.TraverseAndUnpack(unpacker); }
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> /// Serializes the table of contents and its B-Trees. /// </summary> public byte[] Serialize() { using var ms = new MemoryStream(); using var bs = new BinaryStream(ms, ByteConverter.Big); bs.Write(SEGMENT_MAGIC); bs.Position = 16; bs.WriteUInt32((uint)Files.Count); bs.Position += sizeof(uint) * Files.Count; uint fileNamesOffset = (uint)bs.Position; FileNames.Serialize(bs); uint extOffset = (uint)bs.Position; Extensions.Serialize(bs); uint fileInfoOffset = (uint)bs.Position; FileInfos.Serialize(bs); // The list of file entry btrees mostly consist of the relation between files, folder, extensions and data // Thus it is writen at the end // Each tree is a seperate general subdir const int baseListPos = 20; for (int i = 0; i < Files.Count; i++) { FileEntryBTree f = Files[i]; uint treeOffset = (uint)bs.Position; bs.Position = baseListPos + (i * sizeof(uint)); bs.WriteUInt32(treeOffset); bs.Position = treeOffset; f.Serialize(bs, (uint)FileNames.Entries.Count, (uint)Extensions.Entries.Count); } // Go back to write the meta data bs.Position = 4; bs.WriteUInt32(fileNamesOffset); bs.WriteUInt32(extOffset); bs.WriteUInt32(fileInfoOffset); return(ms.ToArray()); }
/// <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); }