下载ChessBase Reader并稍微玩弄ProcMon以找到读取存档和写入数据文件的功能后,我将整个内容加载到IDA中进行分析。数据是霍夫曼编码的。
每个数据块具有以下结构。请注意,霍夫曼压缩使用位而不是字节,因此下表中的每个大小也以位为单位。例如,块长度为 16 位或 2 个字节。
+----------------------------------------------+
| |
|16 bits - uncompressed block length (len) |
| |
+-----------+----------------------------------+
| | |
|Repeat | 4 bits - length of entry (n) |
|256 | |
|times +----------------------------------+
| | |
|one entry | n bits - tree left/right |
|per byte | information for this byte |
|(0-255) | |
| | |
+-----------+----------------------------------+
| |
| Huffman encoded bit sequences. The number of |
| bits isn't stored anywhere, but the number |
| of sequences, which is equal to the number |
| of output bytes, is the block length (len) |
| |
+----------------------------------------------+
假设在这个方案中编码了“foobar”这个词,这可能会导致(我组成了字符的位值):
+----------------+
|Huffman code for|
|character is |
+--------+-------+
| | |
| o | 0 |
| f | 100 |
| b | 101 |
| a | 110 |
| r | 111 |
| | |
+--------+-------+
这将导致单词 foobar 被编码为
100 0 0 101 110 111
. 长度为 6 个字节,或0000 0000 0000 0110
16 位。
foobar
格式化为上表的位数组 for将读取
0000 0000 0000 0110 (16 bit output length)
..... array index 0 for byte '\0'
..... array index 1 for byte '\1'
.....
0011 110 array index 97 for byte 'a' (3 bits)
0011 101 array index 98 for byte 'b' (3 bits)
.....
0011 100 array index 102 for byte 'f' (3 bits)
.....
0001 0 array index 111 for byte 'o' (1 bit)
.....
0011 101 array index 114 for byte 'r' (3 bits)
..... remaining bit combos - 255
100 0 0 101 110 111 foobar text
该实现从代码表构建一个二叉树。当它读取数据时,它从树的根开始;每个位沿树向下移动,向左或向右移动,具体取决于下一个位值。当到达叶子节点时,输出相应的字节。这将重复直到达到输出流的长度。
二进制文件中的相关函数是:
BECAA0
:解码存档数据。读取16位长度;然后在解码器类中的偏移量080A
(位)和0E10
(位长)处将编码表读入两个数组中。在此之后,调用BEC930
解码数据字节。
BEBF30
:一个参数(位数),从输入数组中获取这么多位。在函数的末尾,offset 处的字1014
具有这些位。
BEBAD0
: 从位于080A
和的数组构建树0E10
BEC930
: 调用BEBAD0
构建树,然后从输入流中读取剩余的位。为每一位遍历树;当找到叶子时发出一个字节。最后,调用BEBA90
销毁树。
BEBA90
:通过删除左右子节点递归删除节点,即节点本身。
如果您想阅读文件,我认为调试编写器不会更容易;压缩有很多逻辑和数据结构,知道“一种方式”是如何工作的并不一定能帮助您解决问题。在这种情况下,幸运的是,它是一个众所周知的算法,但是如果算法未知,如果您只知道如何解压缩,则很难有效地压缩。