using System; using System.IO; using System.Collections; using System.Collections.Specialized; namespace HtmlHelp.ChmDecoding { /// /// The class CHMBtree implements methods/properties to decode the binary help index. /// This class automatically creates an index arraylist for the current CHMFile instance. /// It does not store the index internally ! /// /// The binary index can be found in the storage file $WWKeywordLinks/BTree internal sealed class CHMBtree : IDisposable { /// /// Constant specifying the size of the string blocks /// private const int BLOCK_SIZE = 2048; /// /// Internal flag specifying if the object is going to be disposed /// private bool disposed = false; /// /// Internal member storing the binary file data /// private byte[] _binaryFileData = null; /// /// Internal member storing flags /// private int _flags = 0; /// /// Internal member storing the data format /// private byte[] _dataFormat = new byte[16]; /// /// Internal member storing the index of the last listing block /// private int _indexOfLastListingBlock = 0; /// /// Internal member storing the index of the root block /// private int _indexOfRootBlock = 0; /// /// Internal member storing the number of blocks /// private int _numberOfBlocks = 0; /// /// Internal member storing the tree depth. /// (1 if no index blocks, 2 one level of index blocks, ...) /// private int _treeDepth = 0; /// /// Internal member storing the number of keywords in the file /// private int _numberOfKeywords = 0; /// /// Internal member storing the codepage /// private int _codePage = 0; /// /// true if the index is from a CHI or CHM file, else CHW /// private bool _isCHI_CHM = true; /// /// Internal member storing the associated chmfile object /// private CHMFile _associatedFile = null; /// /// Internal flag specifying if we have to read listing or index blocks /// private bool _readListingBlocks = true; /// /// Internal member storing an indexlist of the current file. /// private ArrayList _indexList = new ArrayList(); /// /// Constructor of the class /// /// binary file data of the $WWKeywordLinks/BTree file /// associated chm file public CHMBtree(byte[] binaryFileData, CHMFile associatedFile) { if( associatedFile == null) { throw new ArgumentException("CHMBtree.ctor() - Associated CHMFile must not be null !", "associatedFile"); } _binaryFileData = binaryFileData; _associatedFile = associatedFile; DecodeData(); // clear internal binary data after extraction _binaryFileData = null; } /// /// Decodes the binary file data and fills the internal properties /// /// true if succeeded private bool DecodeData() { bool bRet = true; MemoryStream memStream = new MemoryStream(_binaryFileData); BinaryReader binReader = new BinaryReader(memStream); int nCurOffset = 0; int nTemp = 0; // decode header binReader.ReadChars(2); // 2chars signature (not important) _flags = (int)binReader.ReadInt16(); // WORD flags binReader.ReadInt16(); // size of blocks (always 2048) _dataFormat = binReader.ReadBytes(16); binReader.ReadInt32(); // unknown DWORD _indexOfLastListingBlock = binReader.ReadInt32(); _indexOfRootBlock = binReader.ReadInt32(); binReader.ReadInt32(); // unknown DWORD _numberOfBlocks = binReader.ReadInt32(); _treeDepth = binReader.ReadInt16(); _numberOfKeywords = binReader.ReadInt32(); _codePage = binReader.ReadInt32(); binReader.ReadInt32(); // lcid DWORD nTemp = binReader.ReadInt32(); _isCHI_CHM = (nTemp==1); binReader.ReadInt32(); // unknown DWORD binReader.ReadInt32(); // unknown DWORD binReader.ReadInt32(); // unknown DWORD binReader.ReadInt32(); // unknown DWORD // end of header decode while( (memStream.Position < memStream.Length) && (bRet) ) { nCurOffset = (int)memStream.Position; byte [] dataBlock = binReader.ReadBytes(BLOCK_SIZE); bRet &= DecodeBlock(dataBlock, ref nCurOffset, _treeDepth-1); } return bRet; } /// /// Decodes a block of url-string data /// /// block of data /// current file offset /// number of index blocks /// true if succeeded private bool DecodeBlock( byte[] dataBlock, ref int nOffset, int indexBlocks ) { bool bRet = true; int nblockOffset = nOffset; MemoryStream memStream = new MemoryStream(dataBlock); BinaryReader binReader = new BinaryReader(memStream); int freeSpace = binReader.ReadInt16(); // length of freespace int nrOfEntries = binReader.ReadInt16(); // number of entries bool bListingEndReached = false; //while( (memStream.Position < (memStream.Length-freeSpace)) && (bRet) ) //{ int nIndexOfPrevBlock = -1; int nIndexOfNextBlock = -1; int nIndexOfChildBlock = 0; if(_readListingBlocks) { nIndexOfPrevBlock = binReader.ReadInt32(); // -1 if this is the header nIndexOfNextBlock = binReader.ReadInt32(); // -1 if this is the last block } else { nIndexOfChildBlock = binReader.ReadInt32(); } for(int nE = 0; nE < nrOfEntries; nE++) { if(_readListingBlocks) { bListingEndReached = (nIndexOfNextBlock==-1); string keyWord = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding); bool isSeeAlsoKeyword = (binReader.ReadInt16()!=0); int indent = binReader.ReadInt16(); // indent of entry int nCharIndex = binReader.ReadInt32(); binReader.ReadInt32(); int numberOfPairs = binReader.ReadInt32(); int[] nTopics = new int[numberOfPairs]; string[] seeAlso = new string[numberOfPairs]; for(int i=0; i < numberOfPairs; i++) { if(isSeeAlsoKeyword) { seeAlso[i] = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding); } else { nTopics[i] = binReader.ReadInt32(); } } binReader.ReadInt32(); // unknown int nIndexOfThisEntry = binReader.ReadInt32(); IndexItem newItem = new IndexItem(_associatedFile, keyWord, isSeeAlsoKeyword, indent, nCharIndex, nIndexOfThisEntry, seeAlso, nTopics); _indexList.Add(newItem); } else { string keyWord = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding); bool isSeeAlsoKeyword = (binReader.ReadInt16()!=0); int indent = binReader.ReadInt16(); // indent of entry int nCharIndex = binReader.ReadInt32(); binReader.ReadInt32(); int numberOfPairs = binReader.ReadInt32(); int[] nTopics = new int[numberOfPairs]; string[] seeAlso = new string[numberOfPairs]; for(int i=0; i < numberOfPairs; i++) { if(isSeeAlsoKeyword) { seeAlso[i] = BinaryReaderHelp.ExtractUTF16String(ref binReader, 0, true, _associatedFile.TextEncoding); } else { nTopics[i] = binReader.ReadInt32(); } } int nIndexChild = binReader.ReadInt32(); int nIndexOfThisEntry=-1; IndexItem newItem = new IndexItem(_associatedFile, keyWord, isSeeAlsoKeyword, indent, nCharIndex, nIndexOfThisEntry, seeAlso, nTopics); _indexList.Add(newItem); } } //} binReader.ReadBytes(freeSpace); if( bListingEndReached ) _readListingBlocks = false; return bRet; } /// /// Gets the internal generated index list /// internal ArrayList IndexList { get { return _indexList; } } /// /// Implement IDisposable. /// public void Dispose() { Dispose(true); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SupressFinalize to // take this object off the finalization queue // and prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); } /// /// Dispose(bool disposing) executes in two distinct scenarios. /// If disposing equals true, the method has been called directly /// or indirectly by a user's code. Managed and unmanaged resources /// can be disposed. /// If disposing equals false, the method has been called by the /// runtime from inside the finalizer and you should not reference /// other objects. Only unmanaged resources can be disposed. /// /// disposing flag private void Dispose(bool disposing) { // Check to see if Dispose has already been called. if(!this.disposed) { // If disposing equals true, dispose all managed // and unmanaged resources. if(disposing) { // Dispose managed resources. _binaryFileData = null; } } disposed = true; } } }