| 167 | | class ChmFile(Parser): |
|---|
| | 183 | class NameList(FieldSet): |
|---|
| | 184 | def createFields(self): |
|---|
| | 185 | yield UInt16(self, "length", "Length of name list in 2-byte blocks") |
|---|
| | 186 | yield UInt16(self, "count", "Number of entries in name list") |
|---|
| | 187 | for index in range(self["count"].value): |
|---|
| | 188 | length=UInt16(self, "name_len[]", "Length of name in 2-byte blocks, excluding terminating null") |
|---|
| | 189 | yield length |
|---|
| | 190 | yield String(self, "name[]", length.value*2+2, charset="UTF-16-LE") |
|---|
| | 191 | |
|---|
| | 192 | class ControlData(FieldSet): |
|---|
| | 193 | def createFields(self): |
|---|
| | 194 | yield UInt32(self, "count", "Number of DWORDS in this struct") |
|---|
| | 195 | yield String(self, "type", 4, "Type of compression") |
|---|
| | 196 | if self["type"].value!='LZXC': return |
|---|
| | 197 | yield UInt32(self, "version", "Compression version") |
|---|
| | 198 | version=self["version"].value |
|---|
| | 199 | if version==1: block='bytes' |
|---|
| | 200 | else: block='32KB blocks' |
|---|
| | 201 | yield UInt32(self, "reset_interval", "LZX: Reset interval in %s"%block) |
|---|
| | 202 | yield UInt32(self, "window_size", "LZX: Window size in %s"%block) |
|---|
| | 203 | yield UInt32(self, "cache_size", "LZX: Cache size in %s"%block) |
|---|
| | 204 | yield UInt32(self, "unknown[]") |
|---|
| | 205 | |
|---|
| | 206 | class ResetTable(FieldSet): |
|---|
| | 207 | def createFields(self): |
|---|
| | 208 | yield UInt32(self, "unknown[]", "Version number?") |
|---|
| | 209 | yield UInt32(self, "count", "Number of entries") |
|---|
| | 210 | yield UInt32(self, "entry_size", "Size of each entry") |
|---|
| | 211 | yield UInt32(self, "header_size", "Size of this header") |
|---|
| | 212 | yield UInt64(self, "uncompressed_size") |
|---|
| | 213 | yield UInt64(self, "compressed_size") |
|---|
| | 214 | yield UInt64(self, "block_size", "Block size in bytes") |
|---|
| | 215 | for i in xrange(self["count"].value): |
|---|
| | 216 | yield UInt64(self, "block_location[]", "location in compressed data of 1st block boundary in uncompressed data") |
|---|
| | 217 | |
|---|
| | 218 | class SystemEntry(FieldSet): |
|---|
| | 219 | ENTRY_TYPE={0:"HHP: [OPTIONS]: Contents File", |
|---|
| | 220 | 1:"HHP: [OPTIONS]: Index File", |
|---|
| | 221 | 2:"HHP: [OPTIONS]: Default Topic", |
|---|
| | 222 | 3:"HHP: [OPTIONS]: Title", |
|---|
| | 223 | 4:"File Metadata", |
|---|
| | 224 | 5:"HHP: [OPTIONS]: Default Window", |
|---|
| | 225 | 6:"HHP: [OPTIONS]: Compiled file", |
|---|
| | 226 | # 7 present only in files with Binary Index; unknown function |
|---|
| | 227 | # 8 unknown function |
|---|
| | 228 | 9: "Version", |
|---|
| | 229 | 10: "Timestamp", |
|---|
| | 230 | # 11 only in Binary TOC files |
|---|
| | 231 | 12: "Number of Info Types", |
|---|
| | 232 | 13: "#IDXHDR file", |
|---|
| | 233 | # 14 unknown function |
|---|
| | 234 | # 15 checksum?? |
|---|
| | 235 | 16:"HHP: [OPTIONS]: Default Font", |
|---|
| | 236 | } |
|---|
| | 237 | def createFields(self): |
|---|
| | 238 | yield Enum(UInt16(self, "type", "Type of entry"),self.ENTRY_TYPE) |
|---|
| | 239 | yield UInt16(self, "length", "Length of entry") |
|---|
| | 240 | yield RawBytes(self, "data", self["length"].value) |
|---|
| | 241 | def createDescription(self): |
|---|
| | 242 | return '#SYSTEM Entry, Type %s'%self["type"].display |
|---|
| | 243 | |
|---|
| | 244 | class SystemFile(FieldSet): |
|---|
| | 245 | def createFields(self): |
|---|
| | 246 | yield UInt32(self, "version", "Either 2 or 3") |
|---|
| | 247 | while self.current_size < self.size: |
|---|
| | 248 | yield SystemEntry(self, "entry[]") |
|---|
| | 249 | |
|---|
| | 250 | class ChmFile(HachoirParser, RootSeekableFieldSet): |
|---|
| 194 | | size = (self.size - self.current_size) // 8 |
|---|
| 195 | | if size: |
|---|
| 196 | | yield RawBytes(self, "raw_end", size) |
|---|
| | 280 | otherentries = {} |
|---|
| | 281 | for pmgl in directory.array("pmgl"): |
|---|
| | 282 | for entry in pmgl.array("entry"): |
|---|
| | 283 | if entry["section"].value != 0: |
|---|
| | 284 | otherentries.setdefault(entry["section"].value,[]).append(entry) |
|---|
| | 285 | continue |
|---|
| | 286 | if entry["length"].value == 0: |
|---|
| | 287 | continue |
|---|
| | 288 | self.seekByte(self["itsf/data_offset"].value+entry["start"].value) |
|---|
| | 289 | name = entry["name"].value |
|---|
| | 290 | if name == "::DataSpace/NameList": |
|---|
| | 291 | yield NameList(self, "name_list") |
|---|
| | 292 | elif name.startswith('::DataSpace/Storage/'): |
|---|
| | 293 | sectname = str(name.split('/')[2]) |
|---|
| | 294 | if name.endswith('/SpanInfo'): |
|---|
| | 295 | yield UInt64(self, "%s_spaninfo"%sectname, "Size of uncompressed data in the %s section"%sectname) |
|---|
| | 296 | elif name.endswith('/ControlData'): |
|---|
| | 297 | yield ControlData(self, "%s_controldata"%sectname, "Data about the compression scheme", size=entry["length"].value*8) |
|---|
| | 298 | elif name.endswith('/Transform/List'): |
|---|
| | 299 | yield String(self, "%s_transform_list"%sectname, 38, description="Transform/List element", charset="UTF-16-LE") |
|---|
| | 300 | elif name.endswith('/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable'): |
|---|
| | 301 | yield ResetTable(self, "%s_reset_table"%sectname, "LZX Reset Table", size=entry["length"].value*8) |
|---|
| | 302 | elif name.endswith('/Content'): |
|---|
| | 303 | # eventually, a LZX wrapper will appear here, we hope! |
|---|
| | 304 | yield RawBytes(self, "%s_content"%sectname, entry["length"].value, "Content for the %s section"%sectname) |
|---|
| | 305 | else: |
|---|
| | 306 | yield RawBytes(self, "entry_data[]", entry["length"].value, name) |
|---|
| | 307 | elif name=="/#SYSTEM": |
|---|
| | 308 | yield SystemFile(self, "system_file", size=entry["length"].value*8) |
|---|
| | 309 | else: |
|---|
| | 310 | yield RawBytes(self, "entry_data[]", entry["length"].value, name) |
|---|