using System; using System.IO; using System.Collections; namespace HtmlHelp.ChmDecoding { /// <summary> /// The class <c>CHMTocidx</c> implements functions to decode the #TOCIDX internal file. /// </summary> internal sealed class CHMTocidx : IDisposable { /// <summary> /// Constant specifying the size of the data blocks /// </summary> private const int BLOCK_SIZE = 0x1000; /// <summary> /// Internal flag specifying if the object is going to be disposed /// </summary> private bool disposed = false; /// <summary> /// Internal member storing the binary file data /// </summary> private byte[] _binaryFileData = null; /// <summary> /// Internal memebr storing the offset to the 20/28 byte structs /// </summary> private int _offset2028 = 0; /// <summary> /// Internal member storing the offset to the 16 byte structs /// </summary> private int _offset16structs = 0; /// <summary> /// Internal member storing the number of 16 byte structs /// </summary> private int _numberOf16structs = 0; /// <summary> /// Internal member storing the offset to the topic list /// </summary> private int _offsetOftopics = 0; /// <summary> /// Internal member storing the toc /// </summary> private ArrayList _toc = new ArrayList(); /// <summary> /// Internal member for offset seeking /// </summary> private Hashtable _offsetTable = new Hashtable(); /// <summary> /// Internal member storing the associated chmfile object /// </summary> private CHMFile _associatedFile = null; /// <summary> /// Constructor of the class /// </summary> /// <param name="binaryFileData">binary file data of the #TOCIDX file</param> /// <param name="associatedFile">associated chm file</param> public CHMTocidx(byte[] binaryFileData, CHMFile associatedFile) { _binaryFileData = binaryFileData; _associatedFile = associatedFile; DecodeData(); // clear internal binary data after extraction _binaryFileData = null; } /// <summary> /// Decodes the binary file data and fills the internal properties /// </summary> /// <returns>true if succeeded</returns> private bool DecodeData() { _toc = new ArrayList(); _offsetTable = new Hashtable(); bool bRet = true; MemoryStream memStream = new MemoryStream(_binaryFileData); BinaryReader binReader = new BinaryReader(memStream); int nCurOffset = 0; _offset2028 = binReader.ReadInt32(); _offset16structs = binReader.ReadInt32(); _numberOf16structs = binReader.ReadInt32(); _offsetOftopics = binReader.ReadInt32(); binReader.BaseStream.Seek( _offset2028, SeekOrigin.Begin ); if( RecursivelyBuildTree(ref binReader, _offset2028, _toc, null) ) { binReader.BaseStream.Seek( _offset16structs, SeekOrigin.Begin ); nCurOffset = (int)binReader.BaseStream.Position; for(int i=0; i < _numberOf16structs; i++) { int tocOffset = binReader.ReadInt32(); int sqNr = binReader.ReadInt32(); int topOffset = binReader.ReadInt32(); int hhctopicIdx = binReader.ReadInt32(); nCurOffset = (int)binReader.BaseStream.Position; int topicIdx = -1; // if the topic offset is within the range of the stream // and is >= the offset of the first topic dword if((topOffset < (binReader.BaseStream.Length - 4)) && (topOffset >= _offsetOftopics)) { // read the index of the topic for this item binReader.BaseStream.Seek( topOffset, SeekOrigin.Begin); topicIdx = binReader.ReadInt32(); binReader.BaseStream.Seek( nCurOffset, SeekOrigin.Begin); TOCItem item = (TOCItem)_offsetTable[tocOffset.ToString()]; if( item != null) { if(( topicIdx < _associatedFile.TopicsFile.TopicTable.Count)&&(topicIdx>=0)) { TopicEntry te = (TopicEntry) (_associatedFile.TopicsFile.TopicTable[topicIdx]); if( (te != null) && (item.TopicOffset < 0) ) { item.TopicOffset = te.EntryOffset; } } } } } } return bRet; } /// <summary> /// Recursively reads the binary toc tree from the file /// </summary> /// <param name="binReader">reference to binary reader</param> /// <param name="NodeOffset">offset of the first node in the current level</param> /// <param name="level">arraylist of TOCItems for the current level</param> /// <param name="parentItem">parent item for the item</param> /// <returns>Returns true if succeeded</returns> private bool RecursivelyBuildTree(ref BinaryReader binReader, int NodeOffset, ArrayList level, TOCItem parentItem) { bool bRet = true; int nextOffset=0; int nReadOffset = (int)binReader.BaseStream.Position; binReader.BaseStream.Seek(NodeOffset, SeekOrigin.Begin); do { int nCurOffset = (int)binReader.BaseStream.Position; int unkn1 = binReader.ReadInt16(); // unknown int unkn2 = binReader.ReadInt16(); // unknown int flag = binReader.ReadInt32(); int nFolderAdd = 0; if((_associatedFile != null) && (_associatedFile.ImageTypeFolder)) { // get the value which should be added, to display folders instead of books if(HtmlHelpSystem.UseHH2TreePics) nFolderAdd = 8; else nFolderAdd = 4; } int nFolderImgIdx = (HtmlHelpSystem.UseHH2TreePics ? (TOCItem.STD_FOLDER_HH2+nFolderAdd) : (TOCItem.STD_FOLDER_HH1+nFolderAdd)); int nFileImgIdx = (HtmlHelpSystem.UseHH2TreePics ? TOCItem.STD_FILE_HH2 : TOCItem.STD_FILE_HH1); int stdImage = ((flag & 0x4)!=0) ? nFolderImgIdx : nFileImgIdx; int stringOffset = binReader.ReadInt32(); int ParentOffset = binReader.ReadInt32(); nextOffset = binReader.ReadInt32(); int firstChildOffset = 0; int unkn3=0; if( (flag&0x4)!=0 ) { firstChildOffset = binReader.ReadInt32(); unkn3 = binReader.ReadInt32(); // unknown } TOCItem newItem = new TOCItem(); newItem.ImageIndex = stdImage; newItem.Offset = nCurOffset; newItem.OffsetNext = nextOffset; newItem.AssociatedFile = _associatedFile; newItem.TocMode = DataMode.Binary; newItem.Parent = parentItem; if( (flag&0x08) == 0) { // toc item doesn't have a local value (=> stringOffset = offset of strings file) newItem.Name = _associatedFile.StringsFile[stringOffset]; } else { // this item has a topic entry (=> stringOffset = index of topic entry) if((stringOffset < _associatedFile.TopicsFile.TopicTable.Count) && (stringOffset >= 0)) { TopicEntry te = (TopicEntry) (_associatedFile.TopicsFile.TopicTable[stringOffset]); if(te != null) { newItem.TopicOffset = te.EntryOffset; } } } _offsetTable[nCurOffset.ToString()] = newItem; // if this item has children (firstChildOffset > 0) if( firstChildOffset > 0) { bRet &= RecursivelyBuildTree(ref binReader, firstChildOffset, newItem.Children, newItem); } level.Add( newItem ); if(nCurOffset != nextOffset) binReader.BaseStream.Seek(nextOffset, SeekOrigin.Begin); }while(nextOffset != 0); binReader.BaseStream.Seek(nReadOffset, SeekOrigin.Begin); return bRet; } /// <summary> /// Gets the internal read toc /// </summary> internal ArrayList TOC { get { return _toc; } } /// <summary> /// Implement IDisposable. /// </summary> 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); } /// <summary> /// 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. /// </summary> /// <param name="disposing">disposing flag</param> 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; _toc = null; _offsetTable = null; } } disposed = true; } } }