using System; using System.Collections; using System.Diagnostics; using System.IO; using System.Text; using System.Globalization; namespace HtmlHelp.ChmDecoding { /// /// The class CHMSystem reads the #SYSTEM file of the chm and stores its settings /// internal sealed class CHMSystem : IDisposable { /// /// 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 the file version /// private int _fileVersion = 0; /// /// Internal member storing the contents file path /// private string _contentsFile = ""; /// /// Internal member storing the index file path /// private string _indexFile = ""; /// /// Internal member storing the default help topic /// private string _defaultTopic = ""; /// /// Internal member storing the help-window title /// private string _title = ""; /// /// Internal flag if dbcs is on /// private bool _dbcs = false; /// /// Internal flag if fulltext search is enabled /// private bool _fullTextSearch = false; /// /// Internal flag if KLinks are in the file /// private bool _hasKLinks = false; /// /// Internal flag if ALinks are in the file /// private bool _hasALinks = false; /// /// Internal member storing the name of the default window /// private string _defaultWindow = ""; /// /// Internal member storing the filename of the compiled file /// private string _compileFile = ""; /// /// Internal flag storing the offset value of the binary index /// private uint _binaryIndexURLTableID = 0; /// /// Inernal member storing the compiler version this file was compiled /// private string _compilerVersion = ""; /// /// Internal flag storing the offset value of the binary TOC /// private uint _binaryTOCURLTableID = 0; /// /// Internal member storing the associated chmfile object /// private CHMFile _associatedFile = null; /// /// Internal member storing the default fontface, size, charset /// private string _defaultFont = ""; /// /// Internal member storing the culture info of the file /// private CultureInfo _culture; /// /// Constructor of the class /// /// binary file data of the #SYSTEM file /// associated chm file public CHMSystem(byte[] binaryFileData, CHMFile associatedFile) { _binaryFileData = binaryFileData; _associatedFile = associatedFile; DecodeData(); if(_culture == null) { // Set the text encoder of the chm file to the read charset/codepage _associatedFile.TextEncoding = Encoding.GetEncoding( this.CodePage ); } } /// /// 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); // First entry = DWORD for version number _fileVersion = (int) binReader.ReadInt32(); while( (memStream.Position < memStream.Length) && (bRet) ) { bRet &= DecodeEntry(ref binReader); } return bRet; } /// /// Decodes an #system file entry /// /// binary reader reference /// true if succeeded private bool DecodeEntry(ref BinaryReader binReader) { bool bRet = true; int code = (int) binReader.ReadInt16(); // entry code, WORD int length = (int) binReader.ReadInt16(); // length of entry switch(code) { case 0: { _contentsFile = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); };break; case 1: { _indexFile = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); };break; case 2: { _defaultTopic = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); };break; case 3: { _title = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); };break; case 4: { int nTemp = 0; nTemp = binReader.ReadInt32(); // read DWORD LCID _culture = new CultureInfo(nTemp); if(_culture != null) _associatedFile.TextEncoding = Encoding.GetEncoding(_culture.TextInfo.ANSICodePage); nTemp = binReader.ReadInt32(); // read DWORD DBCS _dbcs = (nTemp == 1); nTemp = binReader.ReadInt32(); // read DWORD Fulltext search _fullTextSearch = (nTemp == 1); nTemp = binReader.ReadInt32(); // read DWORD has klinks _hasKLinks = (nTemp != 0); nTemp = binReader.ReadInt32(); // read DWORD has alinks _hasALinks = (nTemp != 0); // read the rest of code 4 (not important for us) byte[] temp = new byte[length-(5*4)]; temp = binReader.ReadBytes(length-(5*4)); };break; case 5: { _defaultWindow = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); };break; case 6: { _compileFile = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); };break; case 7: { if(_fileVersion > 2) { _binaryIndexURLTableID = (uint) binReader.ReadInt32(); } else { byte[] read = binReader.ReadBytes(length); int i=read.Length; } };break; case 8: { // abbreviation (not interresting for us) byte[] read = binReader.ReadBytes(length); int i=read.Length; };break; case 9: { _compilerVersion = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); };break; case 10: { // timestamp of the file (not interresting for us) byte[] read = binReader.ReadBytes(length); int i=read.Length; };break; case 11: { if(_fileVersion > 2) { _binaryTOCURLTableID = (uint) binReader.ReadInt32(); } else { byte[] read = binReader.ReadBytes(length); int i=read.Length; } };break; case 12: { // number of information bytes byte[] read = binReader.ReadBytes(length); int i=read.Length; };break; case 13: { // copy of file #idxhdr byte[] read = binReader.ReadBytes(length); int i=read.Length; };break; case 14: { // custom tabs for HH viewer byte[] read = binReader.ReadBytes(length); int i=read.Length; };break; case 15: { // a checksum byte[] read = binReader.ReadBytes(length); int i=read.Length; };break; case 16: { // Default Font=string,number,number // The string is the name of the font, the first number is the // point size & the last number is the character set used by the font. // For acceptable values see *_CHARSET defines in wingdi.h from the // Windows SDK or the same file in MinGW or Wine. // Most of the time you will only want to use 0, which is the value for ANSI, // which is the subset of ASCII used by Windows. _defaultFont = BinaryReaderHelp.ExtractString(ref binReader,length, 0, true, _associatedFile.TextEncoding); };break; default: { byte[] temp = new byte[length]; temp = binReader.ReadBytes(length); //bRet = false; int i=temp.Length; };break; } return bRet; } /// /// Reads all HHC files and checks which one has the global object tag. /// This hhc file will be returned as master hhc /// /// list of topics containing the extension hhc /// true if the arraylist contains topic items /// the filename of the found master toc private string GetMasterHHC(ArrayList hhcTopics, bool TopicItemArrayList) { string sRet = ""; if( (hhcTopics!=null) && (hhcTopics.Count > 0) ) { if( TopicItemArrayList ) { if(hhcTopics.Count == 1) { sRet = ((TopicEntry)hhcTopics[0]).Locale; } else { foreach(TopicEntry curEntry in hhcTopics) { CHMStream.CHMStream iw=null; MemoryStream fileObject=null; if( _associatedFile.CurrentStorageWrapper == null) { iw=new CHMStream.CHMStream(); iw.OpenCHM(_associatedFile.ChmFilePath); } else { iw = _associatedFile.CurrentStorageWrapper; } fileObject = iw.OpenStream(curEntry.Locale); if( fileObject != null) { string fileString =_associatedFile.TextEncoding.GetString(fileObject.ToArray(),0,(int)fileObject.Length); fileObject.Close(); if( HHCParser.HasGlobalObjectTag(fileString, _associatedFile) ) { sRet = curEntry.Locale; break; } } } } } else { if(hhcTopics.Count == 1) { sRet = ((string)hhcTopics[0]); } else { foreach(string curEntry in hhcTopics) { CHMStream.CHMStream iw=null; MemoryStream fileObject=null; if( _associatedFile.CurrentStorageWrapper == null) { iw=new CHMStream.CHMStream(); iw.OpenCHM(_associatedFile.ChmFilePath); } else { iw = _associatedFile.CurrentStorageWrapper; } fileObject = iw.OpenStream(curEntry); if( fileObject != null) { string fileString =_associatedFile.TextEncoding.GetString(fileObject.ToArray(),0,(int)fileObject.Length); fileObject.Close(); if( HHCParser.HasGlobalObjectTag(fileString, _associatedFile) ) { sRet = curEntry; break; } } } } } } return sRet; } /// /// Gets the file version of the chm file. /// 2 for Compatibility=1.0, 3 for Compatibility=1.1 /// public int FileVersion { get { return _fileVersion; } } /// /// Gets the contents file name /// public string ContentsFile { get { if( BinaryTOC ) // if the file contains a binary TOC { // make sure the CHMFile instance exists and has loaded the file #URLTBL if( (_associatedFile != null) && (_associatedFile.UrltblFile != null ) ) { // Get an url-table entry by its unique id UrlTableEntry entry = _associatedFile.UrltblFile.GetByUniqueID( this.BinaryTOCURLTableID ); if(entry != null) { // entry found, return the url ( = filename ) return entry.URL; } } } else { if(_contentsFile.Length <= 0) { string sCheck = "Table of Contents.hhc"; // default HHP contents filename if( (_associatedFile != null) && (_associatedFile.TopicsFile != null ) ) { TopicEntry te = _associatedFile.TopicsFile.GetByLocale( sCheck ); if( te == null) { sCheck = "toc.hhc"; // default HHP contents filename te = _associatedFile.TopicsFile.GetByLocale( sCheck ); if( te == null) { sCheck = CompileFile + ".hhc"; te = _associatedFile.TopicsFile.GetByLocale( sCheck ); if( te == null) { ArrayList arrExt = _associatedFile.TopicsFile.GetByExtension("hhc"); if( arrExt == null ) { arrExt = _associatedFile.EnumFilesByExtension("hhc"); if( arrExt == null ) { Debug.WriteLine("CHMSystem.ContentsFile - Failed, contents file not found !"); } else { if(arrExt.Count > 1) { sCheck = GetMasterHHC(arrExt, false); _contentsFile = sCheck; } else { _contentsFile = ((string)arrExt[0]); sCheck = _contentsFile; } } } else { if(arrExt.Count > 1) { sCheck = GetMasterHHC(arrExt, true); _contentsFile = sCheck; } else { _contentsFile = ((TopicEntry)arrExt[0]).Locale; sCheck = _contentsFile; } } } else { _contentsFile = sCheck; } } else { _contentsFile = sCheck; } } else { _contentsFile = sCheck; } } return sCheck; } } return _contentsFile; } } /// /// Gets the index file name /// public string IndexFile { get { if( BinaryIndex ) // if the file contains a binary index { // make sure the CHMFile instance exists and has loaded the file #URLTBL if( (_associatedFile != null) && (_associatedFile.UrltblFile != null ) ) { // Get an url-table entry by its unique id UrlTableEntry entry = _associatedFile.UrltblFile.GetByUniqueID( this.BinaryIndexURLTableID ); if(entry != null) { // entry found, return the url ( = filename ) return entry.URL; } } } else { if(_indexFile.Length <= 0) { string sCheck = "Index.hhk"; // default HHP index filename if( (_associatedFile != null) && (_associatedFile.TopicsFile != null ) ) { TopicEntry te = _associatedFile.TopicsFile.GetByLocale( sCheck ); if( te == null) { sCheck = CompileFile + ".hhk"; te = _associatedFile.TopicsFile.GetByLocale( sCheck ); if( te == null) { ArrayList arrExt = _associatedFile.TopicsFile.GetByExtension("hhk"); if( arrExt == null ) { Debug.WriteLine("CHMSystem.IndexFile - Failed, index file not found !"); } else { _indexFile = ((TopicEntry)arrExt[0]).Locale; sCheck = _indexFile; } } else { _indexFile = sCheck; } } else { _indexFile = sCheck; } } return sCheck; } } return _indexFile; } } /// /// Sets the default topic of this file /// /// new local value of the topic internal void SetDefaultTopic(string local) { _defaultTopic = local; } /// /// Gets the default help topic /// public string DefaultTopic { get { return _defaultTopic; } } /// /// Gets the title of the help window /// public string Title { get { return _title; } } /// /// Gets the flag if DBCS is in use /// public bool DBCS { get { return _dbcs; } } /// /// Gets the flag if full-text-search is available /// public bool FullTextSearch { get { return _fullTextSearch; } } /// /// Gets the flag if the file has ALinks /// public bool HasALinks { get { return _hasALinks; } } /// /// Gets the flag if the file has KLinks /// public bool HasKLinks { get { return _hasKLinks; } } /// /// Gets the default window name /// public string DefaultWindow { get { return _defaultWindow; } } /// /// Gets the file name of the compile file /// public string CompileFile { get { return _compileFile; } } /// /// Gets the id of the binary index in the url table /// public uint BinaryIndexURLTableID { get { return _binaryIndexURLTableID; } } /// /// Gets the flag if the chm has a binary index file /// public bool BinaryIndex { get { return (_binaryIndexURLTableID>0); } } /// /// Gets the flag if the chm has a binary index file /// public string CompilerVersion { get { return _compilerVersion; } } /// /// Gets the id of the binary toc in the url table /// public uint BinaryTOCURLTableID { get { return _binaryTOCURLTableID; } } /// /// Gets the flag if the chm has a binary toc file /// public bool BinaryTOC { get { return (_binaryTOCURLTableID>0); } } /// /// Gets the font face of the read font property. /// Empty string for default font. /// public string FontFace { get { if( _defaultFont.Length > 0) { string [] fontSplit = _defaultFont.Split( new char[]{','}); if(fontSplit.Length > 0) return fontSplit[0].Trim(); } return ""; } } /// /// Gets the font size of the read font property. /// 0 for default font size /// public double FontSize { get { if( _defaultFont.Length > 0) { string [] fontSplit = _defaultFont.Split( new char[]{','}); if(fontSplit.Length > 1) return double.Parse(fontSplit[1].Trim()); } return 0.0; } } /// /// Gets the character set of the read font property /// 1 for default /// public int CharacterSet { get { if( _defaultFont.Length > 0) { string [] fontSplit = _defaultFont.Split( new char[]{','}); if(fontSplit.Length > 2) return Int32.Parse(fontSplit[2].Trim()); } return 0; } } /// /// Gets the codepage depending on the read font property /// public int CodePage { get { // if we've read a LCID from the system file // ignore the font-property settings and return // the codepage generated from the culture info if(_culture != null) { return _culture.TextInfo.ANSICodePage; } int nRet = 1252; // default codepage windows-1252 int nCSet = CharacterSet; switch(nCSet) { case 0x00: nRet = 1252;break; // ANSI_CHARSET case 0xCC: nRet = 1251;break; // RUSSIAN_CHARSET case 0xEE: nRet = 1250;break; // EE_CHARSET case 0xA1: nRet = 1253;break; // GREEK_CHARSET case 0xA2: nRet = 1254;break; // TURKISH_CHARSET case 0xBA: nRet = 1257;break; // BALTIC_CHARSET case 0xB1: nRet = 1255;break; // HEBREW_CHARSET case 0xB2: nRet = 1256;break; // ARABIC_CHARSET case 0x80: nRet = 932;break; // SHIFTJIS_CHARSET case 0x81: nRet = 949;break; // HANGEUL_CHARSET case 0x86: nRet = 936;break; // GB2313_CHARSET case 0x88: nRet = 950;break; // CHINESEBIG5_CHARSET } return nRet; } } /// /// Gets the assiciated culture info /// public CultureInfo Culture { get { return _culture; } } /// /// 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; } } }