CHM格式简介
CHM格式有一个初始化头,占38个字节,后面是header section和到正文的偏移量。加在一起,这些被称为文件头。
header section一共有两个section(有点像信息说明),一个是文件目录,另一个包含着文件长度和一些未知信息。
文件头包含初始化信息,格式如下:
0000:第一个双字为ITSF
0004:第二个双字为版本信息
0008:第三双字是文件头的总长度
000C:第四双字值为1
0010:第五双字是一个时间记录 (第一个字节是MSB,第二个字节是 fractional seconds(second byte),第三个字节可并不确定,第四个字节仅能知道其符号位是确定的。
0014:第六双字是windows语言ID标识
后面16个字节是两个连续的GUID,分别为
0018:{7C01FD10-7BAA-11D0-9E0C-00A0-C922-E6EC}
0028:{7C01FD11-7BAA-11D0-9E0C-00A0-C922-E6EC}
后面是header section的表,其中有两项,每项占16个字节,记录着从文件头开始的偏移量和section的长度,各占8个字节。
0000:从文件起始处开始的section偏移量
0008:section的长度
后面还有8个字节的信息,这些在版本2里是没有的。这个信息是附加头数据。
0000:第一双字0x01fe(未知)
0004:第二双字0(未知)
0008:第三双字为文件大小
0010:第四双字0(未知)
0014:第五双字0(未知)
header section 1(目录列表)
chm文件的中心部分,文件目录和文件信息。它包含如下部分:
0000:第一个双字为ITSP,
0004:第二个双字为版本号为1,
0008:第三双字为目录头长度,
000C:第四双字为0a信息未知,
0010:第五双字值为0x1000,是目录块的大小,
0014:第六双字是quickref section的“密度”,一般是2
0018:第七双字是索引树的深度,1表示没有索引,2表示有一层的PMGI数据块。
001C:第八双字表示根索引的块号,如果没有索引为-1
0020:第九双字是第一个PMGL(listing)的块号
0024:第十双字是最后一个PMGL的块号
0028:第十一双字是-1(未知)
002C:第十二双字是目录块的块数
0030:第十三双字是windows语言ID标识
0034:从这里开始有16个字节的GUID{5D02926A-212E-11D0-9DF9-00A0C922E6EC}
0044:接着的双字是54(又一个长度)
0048:双字都为-1(未知)
004C:双字都为-1(未知)
0050:双字都为-1(未知)
文件头后就是目录块。目录块分为两种,一种是列表块(listing chunks),一种是索引块(index chunks)。如果只有一个列表块,则索引块被省略。
其中列表块的格式如下:
0000:开始是四个字节PMGL
0004:然后的四个字节是目录块尾部的空白区的长度或是quickref区域的长度
0008:第三双字总为0
000C:第四双字是前一个列表块的块号,如果这是第一个块,该值为-1
0010:第五双字是后一个列表块的块号,如果这是最后一块,该值为-1
0014:从这里开始是目录列表项,按文件名排序,并且大小写不分
quickref区是从数据块的后面向前写,每隔n个项出现一个quickref,且n的值为1+(1<<“密度”),其格式从后至前为。例如密度为2,n为5。
块长度-0002:第一个字:整个数据块中的入口数
块长度-0004:第二个字:从第0项到第n项之间的偏移量
块长度-0008:第三个字:从第0项到第2n项之间的偏移量
块长度-000C:第四个字:从第0项到第3n向之间的偏移量
以此类推
目录列表的每一项入口的格式如下:
BYTE:encint型名字长度
BYTEs:后面是UTF-8编码的名称
ENCINT:encint型正文段
ENCINT:encint型偏移量
ENCINT:encint型长度
其中偏移量是从解压缩之后的正文段的开始来计算的,同样长度也是表示解压缩之后的长度。
在目录中存在两种文件,用户数据文件和格式信息文件,格式信息文件以两个连续的冒号“::”开头,用户数据文件以“/”开头。
索引块的格式如下:
0000:前四个字节为PMGI
0004:后面四个字节是块尾部的quickref或是空白区的长度。
0008:从这里开始是目录索引项的开始,
quickref的格式和排列与列表块中相同。
每一个目录索引项的结构如下:
BYTE:encint型的名称长度
BYTEs:UFT-8编码的名称
ENCINT:以此名称开始的列表块的块号。
当有索引块的层次较多时,将不再存储数据块号而是存储下一层的索引号。
解释一下encint型变量的编码规则:
一种可变长度的整型变量,第一个字节只使用低7位,最高位为1表示该字节之后的下一字节的低7位要接在这7位的尾部组成一个数,这样通过移位相加的运算,直到遇到最高位为0的字节,可以组和成一个长度可调节的整数。
在版本3中,正文一般紧跟着文件头,而且在文件头表之后有一个双字用来指定其位置。在版本2中,正文部分紧跟着文件头。所有第0段正文内容在目录中的位置都是相对那点来说的,其它的正文段都在content section 0中。
放在content section 0和目录中的文件,文件名为"::DataSpace/NameList",其中包含着所有正文段的名称,其格式如下:
0000:第一个字:以字计数的文件长度
0002:第二个字:文件中的entry数
对于每一个entry格式为:
0000:第一个字:以字计数的名字长度,不包括最后的NULL结尾符
0002:以word 0表示所有entry的结束。
xxxx:字0
名称的编码类似于UFT-16。
段的名称目前为止只有两种,Uncompressed和MSCompressed,分别表示自解释文件和Microsoft LZX压缩算法压缩的文件。
对于段号不为0的段,还有一个文件为::DataSpace/Storage/<Section Name>/Content,里面存放着该段的压缩信息,所以,当解析
非0段时,需要两步工作,第一步,取得第0段并将其解圧,取得段名,第二步才能利用段名找到相应的段
::DataSpace/Storage/<SectionName>/ControlData
共0x20个字节,存储关于压缩的信息
0000:第一个双字为在“LZXC”串后的双字个数,在版本2中,此值必为6
0004:ASCII第二个双字为“LZXC”
0008:第三个双字为版本信息,必须大于2
000C:第四个双字为LZX reset interval(0x8000字大小的块中)
0010:第五个双字为窗口大小.
0014:第六个双字为缓存大小
0018:第七个双字为0,未知信息。
001C:第八个双字为0,未知信息。
::DataSpace/Storage/<SectionName>/SpanInfo)
存放着未解压的段的长度信息。
::DataSpace/Storage/<SectionName>/Transform/List
存放GUID列表用于解压缩
这一段用LZX压缩,要进行解压缩,先要读取::DataSpace/Storage/<SectionName>/Transform/
{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable,其格式如下:
0000:第一个双字为2,估计是版本信息
0004:第二个双字是reset table中的entry数
0008:第三个双字是8,每一个entry的大小
000C:第四个双字是表头长度
0010:16个字节的压缩前长度
0018:16个字节的压缩后长度
0020:16个字节的0x8000 block size for locations below
0028:16个字节的0
0030:16个字节的第一个非压缩数据块的边界在压缩数据块中的位置信息