using System; using System.IO; using System.Collections; using System.Collections.Specialized; namespace HtmlHelp.ChmDecoding { /// <summary> /// The class <c>CHMUrlstr</c> implements a string collection storing the URL strings of the help file /// </summary> internal sealed class CHMUrlstr : IDisposable { /// <summary> /// Constant specifying the size of the string 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 member storing the url dictionary /// </summary> private Hashtable _urlDictionary = new Hashtable(); /// <summary> /// Internal member storing the framename dictionary /// </summary> private Hashtable _framenameDictionary = 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 #URLSTR file</param> /// <param name="associatedFile">associated chm file</param> public CHMUrlstr(byte[] binaryFileData, CHMFile associatedFile) { _binaryFileData = binaryFileData; _associatedFile = associatedFile; DecodeData(); // clear internal binary data after extraction _binaryFileData = null; } /// <summary> /// Standard constructor /// </summary> internal CHMUrlstr() { } #region Data dumping /// <summary> /// Dump the class data to a binary writer /// </summary> /// <param name="writer">writer to write the data</param> internal void Dump(ref BinaryWriter writer) { writer.Write( _urlDictionary.Count ); if (_urlDictionary.Count != 0) { IDictionaryEnumerator iDictionaryEnumerator = _urlDictionary.GetEnumerator(); while (iDictionaryEnumerator.MoveNext()) { DictionaryEntry dictionaryEntry = (DictionaryEntry)iDictionaryEnumerator.Current; writer.Write( Int32.Parse(dictionaryEntry.Key.ToString()) ); writer.Write( dictionaryEntry.Value.ToString() ); } } writer.Write( _framenameDictionary.Count ); if (_framenameDictionary.Count != 0) { IDictionaryEnumerator iDictionaryEnumerator = _framenameDictionary.GetEnumerator(); while (iDictionaryEnumerator.MoveNext()) { DictionaryEntry dictionaryEntry = (DictionaryEntry)iDictionaryEnumerator.Current; writer.Write( Int32.Parse(dictionaryEntry.Key.ToString()) ); writer.Write( dictionaryEntry.Value.ToString() ); } } } /// <summary> /// Reads the object data from a dump store /// </summary> /// <param name="reader">reader to read the data</param> internal void ReadDump(ref BinaryReader reader) { int i=0; int nCnt = reader.ReadInt32(); for(i=0; i<nCnt;i++) { int nKey = reader.ReadInt32(); string sValue = reader.ReadString(); _urlDictionary[nKey.ToString()] = sValue; } nCnt = reader.ReadInt32(); for(i=0; i<nCnt;i++) { int nKey = reader.ReadInt32(); string sValue = reader.ReadString(); _framenameDictionary[nKey.ToString()] = sValue; } } /// <summary> /// Sets the associated CHMFile instance /// </summary> /// <param name="associatedFile">instance to set</param> internal void SetCHMFile(CHMFile associatedFile) { _associatedFile = associatedFile; } #endregion /// <summary> /// Decodes the binary file data and fills the internal properties /// </summary> /// <returns>true if succeeded</returns> private bool DecodeData() { bool bRet = true; MemoryStream memStream = new MemoryStream(_binaryFileData); BinaryReader binReader = new BinaryReader(memStream); int nCurOffset = 0; while( (memStream.Position < memStream.Length) && (bRet) ) { nCurOffset = (int)memStream.Position; byte [] dataBlock = binReader.ReadBytes(BLOCK_SIZE); bRet &= DecodeBlock(dataBlock, ref nCurOffset); } return bRet; } /// <summary> /// Decodes a block of url-string data /// </summary> /// <param name="dataBlock">block of data</param> /// <param name="nOffset">current file offset</param> /// <returns>true if succeeded</returns> private bool DecodeBlock( byte[] dataBlock, ref int nOffset ) { bool bRet = true; int blockOffset = nOffset; MemoryStream memStream = new MemoryStream(dataBlock); BinaryReader binReader = new BinaryReader(memStream); if(nOffset==0) binReader.ReadByte(); // first block starts with an unknown byte while( (memStream.Position < (memStream.Length-8)) && (bRet) ) { int entryOffset = blockOffset + (int)memStream.Position; int urlOffset = binReader.ReadInt32(); int frameOffset = binReader.ReadInt32(); // There is one way to tell where the end of the URL/FrameName // pairs occurs: Repeat the following: read 2 DWORDs and if both // are less than the current offset then this is the start of the Local // strings else skip two NT strings. // if(( (urlOffset < (entryOffset+8)) && (frameOffset < (entryOffset+8)) )) // { // //TODO: add correct string reading if an offset has been found // /* // int curOffset = (int)memStream.Position; // // memStream.Seek( (long)(blockOffset-urlOffset), SeekOrigin.Begin); // string sTemp = CHMReader.ExtractString(ref binReader, 0, true); // // memStream.Seek( (long)(blockOffset-frameOffset), SeekOrigin.Begin); // sTemp = CHMReader.ExtractString(ref binReader, 0, true); // // memStream.Seek((long)curOffset, SeekOrigin.Begin); // */ // // // int curOffs = (int)memStream.Position; // BinaryReaderHelp.ExtractString(ref binReader, 0, true, _associatedFile.TextEncoding); // nOffset += (int)memStream.Position - curOffs; // // curOffs = (int)memStream.Position; // BinaryReaderHelp.ExtractString(ref binReader, 0, true, _associatedFile.TextEncoding); // nOffset += (int)memStream.Position - curOffs; // } // else { bool bFoundTerminator = false; string sTemp = BinaryReaderHelp.ExtractString(ref binReader, ref bFoundTerminator, 0, true, _associatedFile.TextEncoding); if(sTemp == "") { //nOffset = nOffset + 1 + (int)memStream.Length - (int)memStream.Position; memStream.Seek(memStream.Length-1, SeekOrigin.Begin); } else { _urlDictionary[entryOffset.ToString()] = sTemp.ToString(); _framenameDictionary[ entryOffset.ToString() ] = sTemp.ToString() ; } } } return bRet; } /// <summary> /// Gets the url at a given offset /// </summary> /// <param name="offset">offset of url</param> /// <returns>the url at the given offset</returns> public string GetURLatOffset(int offset) { if(offset == -1) return String.Empty; string sTemp = (string)_urlDictionary[ offset.ToString() ]; if(sTemp == null) return String.Empty; return sTemp; } /// <summary> /// Gets the framename at a given offset /// </summary> /// <param name="offset">offset of the framename</param> /// <returns>the frame name at the given offset</returns> public string GetFrameNameatOffset(int offset) { if(offset == -1) return String.Empty; string sTemp = (string)_framenameDictionary[ offset.ToString() ]; if(sTemp == null) return String.Empty; return sTemp; } /// <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; _urlDictionary = null; _framenameDictionary = null; } } disposed = true; } } }