[轉貼]CHM格式解析

這一篇CHM格式解析,應該是譯自Microsoft’s HTML Help (.chm) format,我把它轉貼在此供紀錄:

HM格式有一个初始化头,占38H字节,后面是header section和到正文 段的偏移量。加在一起,这些被称为文件头。
header section一共有两个section,一个是文件目录,另一个包含着文件长度和一些未知信息。
初始化头:
前 四个字节为ITSF,第二个双字为版本信息,第三双字是文件头的总长度,第四双字值为1,第五双字是一个时间记录,(第一个字节是MSB,第二个字节是 fractional seconds(second byte),第三个字节可并不确定,第四个字节仅能知道其符号位是确定的。)第六双字是windows语言ID标识,后面16个字节是两个连续的组ID, 分别为{7C01FD10-7BAA-11D0-9E0C-00A0-C922-E6EC}
和{7C01FD11-7BAA-11D0-9E0C-00A0-C922-E6EC}
后面是header section的表,其中有两项,每项占16个字节,记录着从文件头开始的偏移量和section的长度,各占8个字节。
后面还有8个字节的信息,这些在版本2里是没有的。
header section 0:
第一双字:0x01fe
第三双字为文件大小
共占5个双字,其余双字均为0
header section 1(directory header)
开始的四个字节为ITSP,
后面的双字为版本号,
第三双字为本section长度,
第四双字信息未知,
第五双字值为0x1000,是目录块的大小,
第六双字是quickref section的“密度”,一般是2
第七双字是索引树的深度,1表示没有索引,2表示有一层的PMGI数据块。
第八双字表示根索引的块号,如果没有索引为-1
第九双字是第一个PMGL(listing)的块号
第十双字是最后一个PMGL的块号
第十一双字是-1
第十二双字是目录块的块数
第十三双字是windows语言ID标识
从这里开始有16个字节的GUID{5D02926A-212E-11D0-9DF9-00A0C922E6EC}
然后四个双字不知道是什么东西
本段共84个字节
从这里开始往后都是数据块,分为两种,一种是列表块(listing chunks),一种是索引块(index chunks)其中列表块的格式如下:
开始是四个字节PMGL
然后的四个字节是目录块尾部的空白区的长度或是quickref区域的长度
第三双字恒为0
第四双字是前一个列表块的块号,如果这是第一个块,该值为-1
第五双字是后一个列表块的块号,如果这是最后一块,该值为-1
从这里开始是目录列表项,按文件名排序,并且大小写不分
quickref区是从数据块的后面向前写,每隔n个项出现一个quickref,且n的值为1+(1<<“密度”),其格式从后至前为
第一个字:整个数据块中的项数
第二个字:从第0项到第n项之间的偏移量
第三个字:从第0项到第2n项之间的偏移量
以此类推
目录列表的每一项的格式如下:
encint型名字长度,后面是UTF-8编码的名称,encint型正文段,encint型偏移量,encint型长度,其中偏移量是从解压缩之后的正文段的开始来计算的,同样长度也是表示解压缩之后的长度。
在目录中存在两种文件,用户数据文件和格式信息文件,格式信息文件以两个连续的冒号“::”开头,用户数据文件以“/”开头。
索引块:
前四个字节为PMGI
后面四个字节是块尾部的quickref或是空白区的长度。
从这里开始是目录索引项的开始,每一个目录索引项的结构如下:
encint型的名称长度,UFT-8编码的名称,以此名称开始的列表块的块号。
quickref的格式和排列与列表块中相同
当有索引块的层次较多时,将不再存储数据块号而是存储下一层的索引号。
解释一下encint型变量的编码规则:
一种可变长度的整型变量,第一个字节只使用低7位,最高位为1表示该字节之后的下一字节的低7位要接在这7位的尾部组成一个数,这样通过移位相加的运算,直到遇到最高位为0的字节,可以组和成一个长度可调节的整数。
正文:在版本3中,正文一般紧跟着文件头,而且在文件头表之后有一个双字用来指定其位置。在版本2中,正文部分紧跟着文件头,而且所有此文件夹中的正文部分的第0段放在都放在这个益上,其它的正文段都within content section 0
名称列表文件:
放在content section 0中,文件名为”::DataSpace/NameList”,其中包含着所有正文段的名称,其格式如下:
第一个字:以字计数的文件长度
第二个字:文件中的entry数
对于每一个entry格式为:
第一个字:以字计数的名字长度,不包括最后的NULL结尾符
以word 0表示所有entry的结束。
名称的编码类似于UFT-16。
段的名称目前为止只有两种,Uncompressed和MSCompressed,分别表示自解释文件和Microsoft LZX压缩算法压缩的文件。
section data:
对 于段号不为0的段,还有一个文件为::DataSpace/Storage/<Section Name>/Content,里面存放着该段的压缩信息,所以,当解析非0段时,需要两步工作,第一步,取得第0段并将其解圧,取得段名,第二步才 能利用段名找到相应的段
其余与格式相关的文件:
::DataSpace/Storage/<SectionName>/ControlData
共0x20个字节,存储关于压缩的信息
第一个双字为在“LZXC”串后的双字个数,在版本2中,此值必为6
第二个双字为“LZXC”
第三个双字为版本信息,必须大于2
第四个双字为LZX reset interval
第五个双字为窗口大小
第六个双字为缓存大小
第七个双字为0,未知信息。
::DataSpace/Storage/<SectionName>/SpanInfo
存放着未解压的段的长度信息。
::DataSpace/Storage/<SectionName>/Transform/List
存放GUID列表用于解压缩
压缩段:
这 一段用LZX压缩,要进行解压缩,先要读取::DataSpace/Storage/<SectionName>/Transform /{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable,其格式如下:
第一个双字为2,估计是版本信息
第二个双字是reset table中的entry数
第三个双字是8,每一个entry的大小
第四个双字是表头长度
16个字节的压缩前长度
16个字节的压缩后长度
16个字节的0x8000 block size for locations below
16个字节的0
16个字节的第一个非压缩数据块的边界在压缩数据块中的位置信息
注意:
There is one change from LZX defined by Microsoft: After each LZX reset interval (defined in the ControlData , but in practice equal to the window size) of compressed data is processed, the LZX state is fully reset, as an entirely new file was being encoded. This allows semi-random access to the compressed data; you can start reading on any reset interval boundary using the reset interval size and the reset table.

Print Friendly, PDF & Email

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料