原文
$ Q2 \9 e% _/ a" |http://dev.gameres.com/Program/Other/LZSS.htm % h& I; E" D3 N3 h
3 ~5 Z2 o& ?' p3 W2 e 业余游戏制作者最头疼的就是没有美工的支持了。很多业余游戏制作所使用的图片都是来自于网上的很有限的一些图片资源,然而这些图片并不能完整配套,所以业余游戏的画面往往显得单调或者搭配不协调(使用多个不属于一系列的图片资源)。基于此,也有不少业余游戏采用“窃取”商业游戏图片于己用的方式(反正业余游戏一般都不用于商业目的),这种方法使用的就是一系列完整、配套的图片,画面就会显得专业、协调得多,但是,前提是能够破解商业游戏的图片格式。多数商业游戏并不会将图片资源以可以直接打开的常用格式存放,而是会做一定的压缩处理,这样做有两个好处:其一,图片不易被用户直接修改或用于其他用途;其二,减小游戏图片资源所占用的磁盘空间。
( H- e8 @0 r* p: H( Q
/ g( T3 i. i1 f6 Q" V9 Q7 E 最近,我为了获取图片资源,研究了一些游戏的图片压缩格式,发现有不少游戏采用了LZSS压缩,例如《天使音乐会》、《神奇传说系列》(只针对《时空道标》和《远征奥德赛》两作,因为没有其他的在手边)。于是,我在网上查阅了一些关于LZSS压缩的资料,实现了这些游戏的图片资源破解。
* |: l5 D" `: f: @
' d8 Z% a0 v. } LZSS压缩是LZ77压缩的改进方式,相对于LZ77减少了冗余度。LZSS在压缩比率上相对其它压缩并没有太大优势,然而它的压缩/解压缩速度却非常快,因此往往用在速度优先的场合(当然,游戏图片解压缩就是速度优先的)。基于这个优势,LZSS被大量采用,例如微软以前常使用的compress.exe/expand.exe就是采用LZSS实现的(这里顺便提一下,《神奇传说时空道标》的图片压缩完全就是用compress.exe的方式压缩的,连文件头都完整存在,因此可以直接用expand.exe来解压缩,就不用自己忙活了^_^)。
, Q- R0 G# o8 q9 m* V$ Q* h' x, n# I5 Z; i: ?6 e
在文章的末尾我给出了一个用LZSS压缩/解压缩的源程序,是Haruhiko Okumura在1989年所写,后来被广泛使用。但是由于其源代码相当晦涩,所以我在这里先把LZSS压缩的原理大致介绍一下:
" ?# l, v6 H( |- \* Z7 `% X1 h' ]) y* O5 [7 a
LZSS采用了一个大小为N的滑动窗口用于在文件中滑动,其中后F大小作为一个前向缓冲,在窗口中前N-F字节内容是已处理部分,而后F字节也就是前向缓冲是待处理部分。如下图示: 4 P2 M } P, N7 N6 Z
- E$ \) L5 y1 q* F4 X
, d: U0 F$ {2 x! N$ s, Q+ x- L5 |9 o4 i5 Q+ ]6 h) B5 P2 B7 n& ^! y$ t
压缩过程就是用前向缓冲中的F字节长的串和前面的N-F个长F的串作比较,例如当F如上图为10的时候,将前向缓冲的qrstabcdfk串分别和前面的zqrstabcdf、yzqrstabcd、xyzqrstabc、wxyzqrstab……总共N-10个串进行比较,寻找最长匹配。在上例中,qrstabcdfk的最长匹配出现在qrstuvwxyz时(即箭头所指位置N-F-10),匹配长度为4即qrst。然后记录下二元组〈匹配位置,匹配长度〉(在上例中是〈N-20, 4〉),放入到输出缓冲区;如果匹配长度少于等于2个字节(例如上例中匹配f时,匹配长度为1),这时用上述二元组记录,反而会造成浪费,因此,直接把原字符放入输出缓冲区。当放入输出缓冲区以后,应该将滑动窗口向后滑动,后F字节中处理过了的字节滑入N-F字节区中,同时从文件中补充相应字节数至后F字节,重复上述处理,直至文件结束。解压缩的过程与此类似,在此不再赘述。 & g [$ {% w! L; K$ p
- p) A( h; i0 ?4 Q& c( ]- l. N1 Q上面只是一个大概的LZSS实现原理,下面我针对所附的源代码中的部分问题做一个解释: " V* N; ]' O C
3 b6 ?# t) _# D, Z+ r$ A7 P6 @1. 大家知道,作字符串比较是比较耗时的,为了提高效率,程序中使用了256棵查找二叉树,每一棵表示一个字节值开头的串,以快速查找到所需要比较的串的所在地。
% Z: k$ X% h9 p% L$ w& K! x: u
! {3 n7 I! u! T3 T& G$ T2. 程序中将滑动窗口做成了一个环状缓冲,以免造成滑动的不便。
; o6 B* k2 D) Z* k% a. X! s: P! f6 B8 v' m D
3. 程序的输出格式如下:用一位表示一个单元的类型,该位为1表示字符未经处理直接输出(一个字节)、为0表示经过了处理,输出上面所说的〈匹配位置,匹配长度〉二元组(两个字节),把这样的8位合在一起(一个字节)表示后面输出的八组元素的类型,其后就是经过处理或未经处理的八组数据,每组一或二个字节,当八组数据满时,将输出缓冲区中的数据输出到文件。二元组的两个字节是这样安排的:第一个字节表示匹配位置的低八位,第二个字节的高四位表示匹配位置的高四位,第二个字节的第四位表示匹配长度(程序中定义N为4096,因此位置值占用12位,F值定义为18,除去匹配长度为1和2的两种情况,共16种情况占4位)。
- E3 w& n& T" Z0 d" i, o6 i% j3 v8 G d6 [( U
4. 微软的compress.exe/expand.exe采用的是N=4096、F=16的定义,因此这个程序只要修改F值就可以解压缩compress.exe压缩的文件(事先需去除文件头)。 ) N" k. k/ {, C! ]7 X( U
/ }( u* B* ~. X$ `- p6 J1 P1 Z4 | e" H- G w4 o5 U
有了这个程序,我们就可以解压不少游戏的图片或其他资源,用于自己的业余游戏开发。如何判断一个文件是否采用LZSS压缩呢?其实很简单,上面我们也说到,在匹配长度小于等于2的时候,LZSS是原样输出的,以BMP文件为例,其文件头前几个字节ASCII为“BM6>[1]”由于BM6等几个字符匹配长度为1,因此在压缩的文件中也肯定是明文出现的,如果其后有类似F3 F0的字节值,就和上面介绍的二元组格式一致了,那么多半就是LZSS压缩的了。 / i. z D! [- }( `+ t* V! ~& S4 l
) y: t5 R' c" D' O
源程序:LZSS.C |