private static void AddNodes(String depth, CFStorage cfs) { VisitedEntryAction va = delegate(ICFItem target) { String temp = target.Name + (target is CFStorage ? "" : " (" + target.Size + " bytes )"); //Stream Console.WriteLine(depth + temp); if (target is CFStorage) { //Storage String newDepth = depth + " "; //Recursion into the storage AddNodes(newDepth, (CFStorage)target); } }; //Visit NON-recursively (first level only) cfs.VisitEntries(va, false); }
/// <summary> /// Recursive addition of tree nodes foreach child of current item in the storage /// </summary> /// <param name="node">Current TreeNode</param> /// <param name="cfs">Current storage associated with node</param> private void AddNodes(TreeNode node, CFStorage cfs) { VisitedEntryAction va = delegate(CFItem target) { TreeNode temp = node.Nodes.Add( target.Name, target.Name + (target.IsStream ? " (" + target.Size + " bytes )" : "") ); temp.Tag = target; if (target.IsStream) { //Stream temp.ImageIndex = 1; temp.SelectedImageIndex = 1; } else { //Storage temp.ImageIndex = 0; temp.SelectedImageIndex = 0; //Recursion into the storage AddNodes(temp, (CFStorage)target); } }; //Visit NON-recursively (first level only) cfs.VisitEntries(va, false); }
/// <summary> /// Get a named storage contained in the current one if existing. /// </summary> /// <param name="storageName">Name of the storage to look for</param> /// <param name="cfStorage">A storage reference if found else null</param> /// <returns><see cref="T:System.Boolean"> true if storage found, else false</returns> /// <example> /// <code> /// /// String FILENAME = "MultipleStorage2.cfs"; /// CompoundFile cf = new CompoundFile(FILENAME, UpdateMode.ReadOnly, false, false); /// /// bool b = cf.RootStorage.TryGetStorage("MyStorage",out CFStorage st); /// /// Assert.IsNotNull(st); /// Assert.IsTrue(b); /// /// cf.Close(); /// </code> /// </example> public bool TryGetStorage(String storageName, out CFStorage cfStorage) { bool result = false; cfStorage = null; try { CheckDisposed(); if (Children.TryLookup(DirectoryEntry.Mock(storageName, StgType.StgInvalid), out IRBNode outDe) && ((IDirectoryEntry)outDe).StgType == StgType.StgStorage) { cfStorage = new CFStorage(this.CompoundFile, outDe as IDirectoryEntry); result = true; } } catch (CFDisposedException) { result = false; } return(result); }
/// <summary> /// Remove an entry from the current storage and compound file. /// </summary> /// <param name="entryName">The name of the entry in the current storage to delete</param> /// <example> /// <code> /// cf = new CompoundFile("A_FILE_YOU_CAN_CHANGE.cfs", UpdateMode.Update, true, false); /// cf.RootStorage.Delete("AStream"); // AStream item is assumed to exist. /// cf.Commit(true); /// cf.Close(); /// </code> /// </example> /// <exception cref="T:OpenMcdf.CFDisposedException">Raised if trying to delete item from a closed compound file</exception> /// <exception cref="T:OpenMcdf.CFItemNotFound">Raised if item to delete is not found</exception> /// <exception cref="T:OpenMcdf.CFException">Raised if trying to delete root storage</exception> public void Delete(String entryName) { CheckDisposed(); // Find entry to delete IDirectoryEntry tmp = DirectoryEntry.Mock(entryName, StgType.StgInvalid); IRBNode foundObj = null; this.Children.TryLookup(tmp, out foundObj); if (foundObj == null) { throw new CFItemNotFound("Entry named [" + entryName + "] was not found"); } //if (foundObj.GetType() != typeCheck) // throw new CFException("Entry named [" + entryName + "] has not the correct type"); if (((IDirectoryEntry)foundObj).StgType == StgType.StgRoot) { throw new CFException("Root storage cannot be removed"); } IRBNode altDel = null; switch (((IDirectoryEntry)foundObj).StgType) { case StgType.StgStorage: CFStorage temp = new CFStorage(this.CompoundFile, ((IDirectoryEntry)foundObj)); // This is a storage. we have to remove children items first foreach (IRBNode de in temp.Children) { IDirectoryEntry ded = de as IDirectoryEntry; temp.Delete(ded.Name); } // ...then we need to rethread the root of siblings tree... if (this.Children.Root != null) { this.DirEntry.Child = (this.Children.Root as IDirectoryEntry).SID; } else { this.DirEntry.Child = DirectoryEntry.NOSTREAM; } // ...and finally Remove storage item from children tree... this.Children.Delete(foundObj, out altDel); // ...and remove directory (storage) entry if (altDel != null) { foundObj = altDel; } this.CompoundFile.InvalidateDirectoryEntry(((IDirectoryEntry)foundObj).SID); break; case StgType.StgStream: // Free directory associated data stream. CompoundFile.FreeAssociatedData((foundObj as IDirectoryEntry).SID); // Remove item from children tree this.Children.Delete(foundObj, out altDel); // Rethread the root of siblings tree... if (this.Children.Root != null) { this.DirEntry.Child = (this.Children.Root as IDirectoryEntry).SID; } else { this.DirEntry.Child = DirectoryEntry.NOSTREAM; } // Delete operation could possibly have cloned a directory, changing its SID. // Invalidate the ACTUALLY deleted directory. if (altDel != null) { foundObj = altDel; } this.CompoundFile.InvalidateDirectoryEntry(((IDirectoryEntry)foundObj).SID); break; } //// Refresh recursively all SIDs (invariant for tree sorting) //VisitedEntryAction action = delegate(CFSItem target) //{ // if( ((IDirectoryEntry)target).SID>foundObj.SID ) // { // ((IDirectoryEntry)target).SID--; // } // ((IDirectoryEntry)target).LeftSibling--; //}; }
/// <summary> /// Create new child storage directory inside the current storage. /// </summary> /// <param name="storageName">The new storage name</param> /// <returns>Reference to the new <see cref="T:OpenMcdf.CFStorage">storage</see></returns> /// <exception cref="T:OpenMcdf.CFDuplicatedItemException">Raised when adding an item with the same name of an existing one</exception> /// <exception cref="T:OpenMcdf.CFDisposedException">Raised when adding a storage to a closed compound file</exception> /// <exception cref="T:OpenMcdf.CFException">Raised when adding a storage with null or empty name</exception> /// <example> /// <code> /// /// String filename = "A_NEW_COMPOUND_FILE_YOU_CAN_WRITE_TO.cfs"; /// /// CompoundFile cf = new CompoundFile(); /// /// CFStorage st = cf.RootStorage.AddStorage("MyStorage"); /// CFStream sm = st.AddStream("MyStream"); /// byte[] b = Helpers.GetBuffer(220, 0x0A); /// sm.SetData(b); /// /// cf.Save(filename); /// /// </code> /// </example> public ICFStorage AddStorage(String storageName) { CheckDisposed(); if (String.IsNullOrEmpty(storageName)) throw new CFException("Stream name cannot be null or empty"); // Add new Storage directory entry CFStorage cfo = null; cfo = new CFStorage(this.CompoundFile); cfo.DirEntry.SetEntryName(storageName); try { // Add object to Siblings tree Children.Add(cfo); } catch (BSTDuplicatedException) { CompoundFile.ResetDirectoryEntry(cfo.DirEntry.SID); cfo = null; throw new CFDuplicatedItemException("An entry with name '" + storageName + "' is already present in storage '" + this.Name + "' "); } CompoundFile.RefreshIterative(Children.Root); this.DirEntry.Child = Children.Root.Value.DirEntry.SID; return cfo; }
//public void DeleteStream(String name) //{ // Delete(name, typeof(CFStream)); //} //public void DeleteStorage(String name) //{ // Delete(name, typeof(CFStorage)); //} /// <summary> /// Remove an entry from the current storage and compound file. /// </summary> /// <param name="entryName">The name of the entry in the current storage to delete</param> /// <example> /// <code> /// cf = new CompoundFile("A_FILE_YOU_CAN_CHANGE.cfs", UpdateMode.Update, true, false); /// cf.RootStorage.Delete("AStream"); // AStream item is assumed to exist. /// cf.Commit(true); /// cf.Close(); /// </code> /// </example> /// <exception cref="T:OpenMcdf.CFDisposedException">Raised if trying to delete item from a closed compound file</exception> /// <exception cref="T:OpenMcdf.CFItemNotFound">Raised if item to delete is not found</exception> /// <exception cref="T:OpenMcdf.CFException">Raised if trying to delete root storage</exception> public void Delete(String entryName) { CheckDisposed(); // Find entry to delete CFMock tmp = new CFMock(entryName, StgType.StgInvalid); CFItem foundObj = null; this.Children.TryFind(tmp, out foundObj); if (foundObj == null) { throw new CFItemNotFound("Entry named [" + entryName + "] was not found"); } //if (foundObj.GetType() != typeCheck) // throw new CFException("Entry named [" + entryName + "] has not the correct type"); if (foundObj.DirEntry.StgType == StgType.StgRoot) { throw new CFException("Root storage cannot be removed"); } switch (foundObj.DirEntry.StgType) { case StgType.StgStorage: CFStorage temp = (CFStorage)foundObj; foreach (CFItem de in temp.Children) { temp.Delete(de.Name); } // Remove item from children tree this.Children.Remove(foundObj); // Synchronize tree with directory entries this.CompoundFile.RefreshIterative(this.Children.Root); // Rethread the root of siblings tree... if (this.Children.Root != null) { this.DirEntry.Child = this.Children.Root.Value.DirEntry.SID; } else { this.DirEntry.Child = DirectoryEntry.NOSTREAM; } // ...and now remove directory (storage) entry this.CompoundFile.RemoveDirectoryEntry(foundObj.DirEntry.SID); break; case StgType.StgStream: // Remove item from children tree this.Children.Remove(foundObj); // Synchronize tree with directory entries this.CompoundFile.RefreshIterative(this.Children.Root); // Rethread the root of siblings tree... if (this.Children.Root != null) { this.DirEntry.Child = this.Children.Root.Value.DirEntry.SID; } else { this.DirEntry.Child = DirectoryEntry.NOSTREAM; } // Remove directory entry this.CompoundFile.RemoveDirectoryEntry(foundObj.DirEntry.SID); break; } //// Refresh recursively all SIDs (invariant for tree sorting) //VisitedEntryAction action = delegate(CFSItem target) //{ // if( ((IDirectoryEntry)target).SID>foundObj.SID ) // { // ((IDirectoryEntry)target).SID--; // } // ((IDirectoryEntry)target).LeftSibling--; //}; }
/// <summary> /// Adds a collection of streams to the hash data. /// /// GameItems and Collections are also hashed, but they are separate streams /// numbered from 1 to n. This assumes they are sequentially numbered and /// adds them all to the current data to hash. /// </summary> /// <param name="hashBuf">Current data to hash</param> /// <param name="streamName">Prefix of the streams to hash</param> /// <param name="stg">Storage of the streams</param> /// <param name="count">Number of streams to hash</param> /// <param name="offset">Offset where to start reading the BIFF data</param> /// <param name="stats">Stats collector</param> private void AddStreams(ICollection<byte[]> hashBuf, string streamName, CFStorage stg, int count, int offset, TableStats stats) { _logger.Info("Adding {0} {1}s...", count, streamName); for (var n = 0; n < count; n++) { AddBiffData(hashBuf, streamName + n, stg, offset, stats); } }
/// <summary> /// Adds a complete stream to the hash data. /// </summary> /// <param name="hashBuf">Current data to hash</param> /// <param name="streamName">Stream to hash</param> /// <param name="stg">Storage of the stream</param> private void AddStream(ICollection<byte[]> hashBuf, string streamName, CFStorage stg) { try { var stream = stg.GetStream(streamName); if (stream != null) { var data = stream.GetData(); hashBuf.Add(data); } } catch (CFItemNotFound) { _logger.Warn("Skipping non-existent Stream {0}.", streamName); } catch (Exception e) { _logger.Error(e, "Error reading data!"); _crashManager.Report(e, "vpt"); } }
/// <summary> /// VPT files offer a way to add custom info through the table info /// dialog. These are key/value pairs that are probably useful /// somewhere. /// /// The *keys* are stored in the `CustomInfoTags` stream of `GameStg`. The /// *values* are separate streams in the `TableInfo` storage. /// /// Since those are also hashed, we need to obtain them and loop through /// them. /// </summary> /// <param name="hashBuf">Current data to hash</param> /// <param name="streamName">Stream name where the keys are stored</param> /// <param name="stg">Storage where the keys are stored</param> /// <param name="info">Storage where the values are stored</param> /// <param name="stats">Stats collector</param> private void AddCustomStreams(ICollection<byte[]> hashBuf, string streamName, CFStorage stg, CFStorage info, TableStats stats) { // retrieve keys var keyBlocks = AddBiffData(hashBuf, streamName, stg, 0, stats); var keys = keyBlocks.Select(block => Encoding.Default.GetString(block.Data.Take(4).ToArray())).ToList(); // read stream for every key _logger.Info("Reading all blocks in {0}: {1}", streamName, string.Join(", ", keys)); keys.ForEach(key => { AddStream(hashBuf, key, info); }); }
/// <summary> /// Adds the data of all BIFF blocks to the hash data. /// </summary> /// <param name="hashBuf">Current data to hash</param> /// <param name="streamName">Name of the stream where BIFF data is stored</param> /// <param name="stg">Storage of the stream</param> /// <param name="offset">Byte offset to start reading BIFF data</param> /// <param name="stats">Stats collector</param> /// <returns>List of parsed BIFF blocks</returns> private List<BiffBlock> AddBiffData(ICollection<byte[]> hashBuf, string streamName, CFStorage stg, int offset, TableStats stats) { // init result var blocks = new List<BiffBlock>(); // get stream from compound document var stream = stg.GetStream(streamName); if (stream == null) { _logger.Warn("No stream {0} in provided storage!", streamName); return blocks; } // get data from stream byte[] buf; try { buf = stream.GetData(); } catch (CFItemNotFound) { _logger.Warn("No data in stream {0}.", streamName); return blocks; } // loop through BIFF blocks var i = offset; do { // Usually, we have: // // [4 bytes] size of block | `blockSize` - not hashed // [blockSize bytes] data, which is: | `block` - hashed // [4 bytes] tag name | `tag` // [blockSize - 4 bytes] real data | `data` // // In case of a string, real data is again prefixed with 4 bytes // of string size, but we don't care because those are hashed too. // // What's NOT hashed is the original block size or the stream block // size, see below. // var blockSize = BitConverter.ToInt32(buf, i); var block = buf.Skip(i + 4).Take(blockSize).ToArray(); // contains tag and data var tag = Encoding.Default.GetString(block.Take(4).ToArray()); // treat exceptions where we hash differently than usual if (tag == "FONT") { // Not hashed, but need to find out how many bytes to skip. Best guess: tag // is followed by 8 bytes of whatever, then 2 bytes size BE, followed by // data. blockSize = BitConverter.ToInt16(buf .Skip(i + 17) .Take(2) .Reverse() // convert to big endian .ToArray(), 0 ); // fonts are ignored, so just update the pointer and continue i += 15; } else if (tag == "CODE") { // In here, the data starts with 4 size bytes again. This is a special case, // what's hashed now is only the tag and the data *after* the 4 size bytes. // concretely, we have: // // [4 bytes] size of block | `blockSize` above // [4 bytes] tag name | `tag` // [4 bytes] size of code | `blockSize` below // [n bytes] code | `block` below // i += 8; blockSize = BitConverter.ToInt32(buf, i); _logger.Info("Code is {0} bytes long.", blockSize); block = buf.Skip(i + 4).Take(blockSize).ToArray(); block = Encoding.Default.GetBytes(tag).Concat(block).ToArray(); } // parse data block if (blockSize > 4) { var data = block.Skip(4).ToArray(); blocks.Add(new BiffBlock(tag, data)); CollectStats(tag, data, stats); } i += blockSize + 4; // finally, add block to hash data hashBuf.Add(block); } while (i < buf.Length - 4); return blocks; }
/// <summary> /// Reads the stored checksum from the table's `GameStg` storage. /// </summary> /// <param name="storage">`GameStg` of the table</param> /// <returns>Checksum</returns> private static byte[] ReadChecksum(CFStorage storage) { return storage.GetStream("MAC").GetData(); }
/// <summary> /// Remove an entry from the current storage and compound file. /// </summary> /// <param name="entryName">The name of the entry in the current storage to delete</param> /// <example> /// <code> /// cf = new CompoundFile("A_FILE_YOU_CAN_CHANGE.cfs", UpdateMode.Update, true, false); /// cf.RootStorage.Delete("AStream"); // AStream item is assumed to exist. /// cf.Commit(true); /// cf.Close(); /// </code> /// </example> /// <exception cref="T:OpenMcdf.CFDisposedException">Raised if trying to delete item from a closed compound file</exception> /// <exception cref="T:OpenMcdf.CFItemNotFound">Raised if item to delete is not found</exception> /// <exception cref="T:OpenMcdf.CFException">Raised if trying to delete root storage</exception> public void Delete(string entryName) { CheckDisposed(); // Find entry to delete var tmp = DirectoryEntry.Mock(entryName, StgType.StgInvalid); Children.TryLookup(tmp, out var foundObj); if (foundObj == null) { throw new CFItemNotFound("Entry named [" + entryName + "] was not found"); } if (((IDirectoryEntry)foundObj).StgType == StgType.StgRoot) { throw new CFException("Root storage cannot be removed"); } IRbNode altDel; // ReSharper disable once SwitchStatementMissingSomeCases switch (((IDirectoryEntry)foundObj).StgType) { case StgType.StgStorage: var temp = new CFStorage(CompoundFile, ((IDirectoryEntry)foundObj)); // This is a storage. we have to remove children items first foreach (var de in temp.Children) { if (de is IDirectoryEntry ded) { temp.Delete(ded.Name); } } // ...then we need to re-thread the root of siblings tree... DirEntry.Child = (Children.Root as IDirectoryEntry)?.SID ?? DirectoryEntry.nostream; // ...and finally Remove storage item from children tree... Children.Delete(foundObj, out altDel); // ...and remove directory (storage) entry if (altDel != null) { foundObj = altDel; } CompoundFile.InvalidateDirectoryEntry(((IDirectoryEntry)foundObj).SID); break; case StgType.StgStream: // Free directory associated data stream. CompoundFile.FreeAssociatedData((foundObj as IDirectoryEntry).SID); // Remove item from children tree Children.Delete(foundObj, out altDel); // Re-thread the root of siblings tree... DirEntry.Child = (Children.Root as IDirectoryEntry)?.SID ?? DirectoryEntry.nostream; // Delete operation could possibly have cloned a directory, changing its SID. // Invalidate the ACTUALLY deleted directory. if (altDel != null) { foundObj = altDel; } CompoundFile.InvalidateDirectoryEntry(((IDirectoryEntry)foundObj).SID); break; } }
private CFStorage GetStorage(CFStorage cfstorage, string storagename) { CFStorage storage = null; try { storage = cfstorage.GetStorage(storagename); } catch (Exception) { } return storage; }
private CFStream GetStream(CFStorage cfstorage, string streamname) { CFStream stream = null; try { stream = cfstorage.GetStream(streamname); } catch (Exception) { } return stream; }