private static BinaryHelpFileHeader ReadFileHeader(Stream stream) { using (BinaryReader reader = new BinaryReader(new StreamView(stream, 0x46, 2))) { BinaryHelpFileHeader header = new BinaryHelpFileHeader(); header.Version = reader.ReadUInt16(); header.Attributes = (HelpFileAttributes)reader.ReadUInt16(); header.ControlCharacter = reader.ReadByte(); header.Padding1 = reader.ReadByte(); header.TopicCount = reader.ReadUInt16(); header.ContextCount = reader.ReadUInt16(); header.DisplayWidth = reader.ReadByte(); header.Padding2 = reader.ReadByte(); header.Padding3 = reader.ReadUInt16(); byte[] stringData = reader.ReadBytes(14); header.DatabaseName = Encoding.ASCII.GetString(stringData); int k = header.DatabaseName.IndexOf('\0'); if (k >= 0) { header.DatabaseName = header.DatabaseName.Substring(0, k); } header.Reserved1 = reader.ReadInt32(); header.TopicIndexOffset = reader.ReadInt32(); header.ContextStringsOffset = reader.ReadInt32(); header.ContextMapOffset = reader.ReadInt32(); header.KeywordsOffset = reader.ReadInt32(); header.HuffmanTreeOffset = reader.ReadInt32(); header.TopicTextOffset = reader.ReadInt32(); header.Reserved2 = reader.ReadInt32(); header.Reserved3 = reader.ReadInt32(); header.DatabaseSize = reader.ReadInt32(); // TODO: make them warnings instead of errors. // COBOL.HLP contains "JCK\r" in Reserved1. #if false // Validate unknown fields. if (header.Version != 2) { throw new NotSupportedException("Unexpected Version field."); } if (header.Padding1 != 0 || header.Padding2 != 0 || header.Padding3 != 0) { throw new NotSupportedException("Unexpected Padding field."); } if (header.Reserved1 != 0 || header.Reserved2 != 0 || header.Reserved3 != 0) { throw new NotSupportedException("Unexpected Reserved field."); } #endif return(header); } }
private static UInt16[] ReadContextMap(BinaryReader reader, BinaryHelpFileHeader header) { if (reader.BaseStream.Position != header.ContextMapOffset) { throw new InvalidDataException("Incorrect Context Map section position."); } UInt16[] contextMap = new UInt16[header.ContextCount]; for (int i = 0; i < header.ContextCount; i++) { contextMap[i] = reader.ReadUInt16(); } return(contextMap); }
private static string[] ReadContextStrings(BinaryReader reader, BinaryHelpFileHeader header) { if (reader.BaseStream.Position != header.ContextStringsOffset) { throw new InvalidDataException("Incorrect Context Strings section position."); } // TODO: the NULL at the very end produces an extra, empty context string. // TODO: check exact number of context strings. int size = header.ContextMapOffset - header.ContextStringsOffset; string all = Encoding.ASCII.GetString(reader.ReadBytes(size)); return(all.Split('\0')); }
private static int[] ReadTopicIndex(BinaryReader reader, BinaryHelpFileHeader header) { if (reader.BaseStream.Position != header.TopicIndexOffset) { throw new InvalidDataException("Incorrect Topic Index section position."); } int[] topicOffsets = new int[header.TopicCount + 1]; for (int i = 0; i <= header.TopicCount; i++) { topicOffsets[i] = reader.ReadInt32(); } return(topicOffsets); }
private static HuffmanTree ReadHuffmanTree(BinaryReader reader, BinaryHelpFileHeader header) { if (reader.BaseStream.Position != header.HuffmanTreeOffset) { throw new InvalidDataException("Incorrect Huffman Tree section position."); } //int sectionSize = file.Header.TopicTextOffset - file.Header.HuffmanTreeOffset; HuffmanTree tree = HuffmanTree.Deserialize(reader); if (tree.IsEmpty || tree.IsSingular) { throw new InvalidDataException("Invalid huffman tree."); } return(tree); }
// TODO: this section doesn't terminate by itself?! private static byte[][] ReadKeywords( BinaryReader reader, BinaryHelpFileHeader header) { if (reader.BaseStream.Position != header.KeywordsOffset) { throw new InvalidDataException("Incorrect Keywords section position."); } int sectionSize = (header.HuffmanTreeOffset > 0 ? header.HuffmanTreeOffset : header.TopicTextOffset) - header.KeywordsOffset; byte[] section = reader.ReadBytes(sectionSize); if (section.Length != sectionSize) { throw new InvalidDataException("Cannot fully read dictionary section."); } return(KeywordListSerializer.Deserialize(section).ToArray()); }
/// <summary> /// Deserializes the next help database from a binary reader. /// </summary> /// <remarks> /// This method throws an exception if it encounters an irrecoverable /// error in the input, such as an IO error or malformed input in the /// meta data. It raises an <c>InvalidTopicData</c> event for each /// format error it encounters during topic deserialization. /// </remarks> public HelpDatabase DeserializeDatabase( Stream stream, SerializationOptions options) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (options == null) { options = new SerializationOptions(); } CheckSignature(stream); BinaryHelpFileHeader header = ReadFileHeader(stream); bool isCaseSensitive = (header.Attributes & HelpFileAttributes.CaseSensitive) != 0; HelpDatabase database = new HelpDatabase(header.DatabaseName, isCaseSensitive); options.ControlCharacter = Graphic437.GetChars(new byte[] { header.ControlCharacter })[0]; using (Stream streamView = new StreamView(stream, header.DatabaseSize, 0x46)) using (BinaryReader reader = new BinaryReader(streamView)) { int[] topicOffsets = ReadTopicIndex(reader, header); // Read Context Strings and Context Map sections. if (true) { string[] contextStrings = ReadContextStrings(reader, header); UInt16[] contextMap = ReadContextMap(reader, header); for (int i = 0; i < header.ContextCount; i++) { database.AddContext(contextStrings[i], contextMap[i]); } } // Read Keywords section. if (header.KeywordsOffset > 0) { options.Keywords = ReadKeywords(reader, header); options.Compression |= CompressionFlags.Keyword; options.Compression |= CompressionFlags.ExtendedKeyword; } else { options.Keywords = null; } // Read Huffman Tree section. if (header.HuffmanTreeOffset > 0) { options.HuffmanTree = ReadHuffmanTree(reader, header); // file.HuffmanTree.Dump(); options.Compression |= CompressionFlags.Huffman; } else { options.HuffmanTree = null; } // Read topic data. if (reader.BaseStream.Position != header.TopicTextOffset) { throw new InvalidDataException("Incorrect topic position."); } for (int i = 0; i < header.TopicCount; i++) { if (reader.BaseStream.Position != topicOffsets[i]) { throw new InvalidDataException("Incorrect topic position."); } int inputLength = topicOffsets[i + 1] - topicOffsets[i]; byte[] inputData = reader.ReadBytes(inputLength); HelpTopic topic = DeserializeTopic(inputData, options); database.Topics.Add(topic); } // TODO: check position if (reader.BaseStream.Position != topicOffsets[header.TopicCount]) { throw new InvalidDataException("Incorrect topic end position."); } if (reader.BaseStream.Position != header.DatabaseSize) { throw new InvalidDataException("Incorrect database size."); } #if DEBUG System.Diagnostics.Debug.WriteLine(string.Format( "Decoded database {0} of {1} bytes.", database.Name, header.DatabaseSize)); #endif } return(database); }