游戏包文件结构分析(1)

游戏包文件结构分析
地狱门神

摘要:本文通过三个例子介绍了如何初步分析一个未知格式的游戏包文件(package file)的文件格式,并从中提取数据。

声明:本文档按现状提供,仅用于研究和学习,因为使用本文档造成的任何损失,本人概不负责。


1.背景

不久之前,一个网友问我怎样从某个游戏的包文件(package file)中提取资源文件,他想从该游戏中提取一个音乐文件作为手机铃声。由于我以前曾经实现过盟军敢死队系列的包文件管理器PCKManager(包含在盟军敢死队开发工具箱中,可以查看该系列的DIR、PCK、PAK文件),对包文件的一些共有特性有一些了解,很快就分析出来了该包文件的格式。后来觉得应该写一篇文章来说明一下,于是就有了这篇文章。文章中有三个例子,分别是盟军敢死队2的PCK文件格式、前面提到的游戏的包文件、盟军敢死队:打击力量的PAK文件格式。
文档中有些地方会提到一些惯例或者背景的知识,希望大家不要觉得繁琐。


2.包文件的共同特征

包文件都有一些共同的东西:一个文件索引表和后面的文件数据。
这个文件索引表一般每条索引至少会包含文件名、文件位置、文件长度三个数据。
其中,有了文件位置(文件在包中的偏移量)和文件长度,一般就可以确定一个文件的数据。
有一些包文件可能会使用文件名的散列值(Hash code)来代替文件名,这种情况很复杂,如果无法通过其他途径找到文件名,可能就没有办法重建文件名,本文暂不考虑这个情况,这里的例子都不存在这个问题。


3.本文需要用到的工具

UltraEdit
Visual Studio/...或者其他任何可以用来编程的东西
Windows自带的科学计算器(既支持16进制和10进制转换,又支持按位逻辑运算And、Or、Xor的好的计算器我没见过,可能以后我会自己做一个)


4.第一个例子——盟军敢死队2的PCK文件格式

这个格式最简单,完全没有任何加密。但是文件索引表采用的一种基于文件夹的索引形式,比下一个例子略复杂,比最后一个例子效率略低(对游戏而言)。
这里你需要电脑上已经装好了盟军敢死队2的游戏。如果你没有这个游戏,可以等到下一节再开始动手操作,下一节的例子只要求下载一个10M左右的小游戏。

用UltraEdit打开DATA.PCK。是不是看到右边的一片“...”中夹杂了一些字符串?这些就是文件名了。

如果你从来没分析过文件的二进制结构,也不要紧张。
在UltraEdit的Hex编辑模式中,最左边一栏是该行最开始的字节的偏移量,中间一栏是文件的所有的字节数据,每一字节用两位16进制数表示。最右边一栏是这些字节数据的ASCII码解释。更准确的说,在中文的系统中,是GB2312的某一种衍生编码解释。因为中文是借用了128-255的扩展ASCII码的空间来表示开始的,所以会出现一些中文字的乱码,这纯粹是巧合。00用点表示,不存在对应的符号的用问号表示。

往下翻几页,你应该会注意到每一个文件名的开始到下一个文件名的开始正好是3行。也就是说,在文件索引表中,一条索引的长度固定为48。

图1 盟军敢死队2 DATA.PCK文件头
00000000h: 44 41 54 41 00 00 00 00 00 00 00 00 00 00 00 00 ; DATA............
00000010h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
00000020h: 00 00 00 00 01 00 00 00 FF FF FF FF 30 00 00 00 ; ........0...
00000030h: 50 41 52 47 4C 4F 42 41 4C 2E 44 41 54 00 00 00 ; PARGLOBAL.DAT...
00000040h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
00000050h: 00 00 00 00 00 00 00 00 DA 2E 00 00 00 A0 03 00 ; ........?...?.
00000060h: 56 41 52 2E 44 41 54 00 00 00 00 00 00 00 00 00 ; VAR.DAT.........
00000070h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
00000080h: 00 00 00 00 00 00 00 00 75 02 00 00 00 D0 03 00 ; ........u....?.
00000090h: 41 4E 49 4D 53 00 00 00 00 00 00 00 00 00 00 00 ; ANIMS...........
000000a0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
000000b0h: 00 00 00 00 01 00 00 00 FF FF FF FF C0 00 00 00 ; ........?..
000000c0h: 41 43 54 49 56 41 44 4F 52 2E 41 4E 32 00 00 00 ; ACTIVADOR.AN2...
000000d0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
000000e0h: 00 00 00 00 00 00 00 00 F2 00 00 00 00 D8 03 00 ; ........?...?.
000000f0h: 41 4C 41 4D 42 52 41 44 41 2E 41 4E 32 00 00 00 ; ALAMBRADA.AN2...
00000100h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
00000110h: 00 00 00 00 00 00 00 00 8A 03 00 00 00 E0 03 00 ; ........?...?.
00000120h: 41 4C 45 41 4C 4D 49 52 41 4E 54 45 2E 41 4E 49 ; ALEALMIRANTE.ANI
00000130h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
00000140h: 00 00 00 00 00 00 00 00 33 D7 00 00 00 E8 03 00 ; ........3?..?.

现在要寻找文件位置。注意到文件名结束之后有一长串零,而零在ASCII中表示什么也没有(null),在C系编程语言中表示一个字符串的结束。我们从"PARGLOBAL.DAT"开始往后看,可以看到在第6行第9个(即58h处),开始出现乱码。
这里的DA 2E 00 00是一个32位整数(Int32)。应该提及的是,整数在文件中有两种常见的存储形式,正序(big-endian)和逆序(little-endian),比如,前面的DA 2E 00 00就是一个逆序表示,其正序表示为00 00 2E DA,即16进制整数0x2EDA,表示成10进制就是11994。在Windows平台下,以逆序为主。
当前,游戏的包文件的大小大多还没有超过2GB(即2^31),因此文件位置和文件大小还可以用32位整数来表示。可以预见,在不久之后,使用64位整数来表示会成为普遍现象。

从58h处往后共八个字节DA 2E 00 00 00 A0 03 00是乱码,后面每个索引都有类似现象,因此有充分的理由来怀疑这两个32位整数就是文件位置和文件大小。事实上,的确是。我们可以在UltraEdit中按Ctrl+G来转到0x3A000处,发现这是一个文件的开头,而在0x2EDA处,仍然是文件索引。因此,第二个整数是文件位置。那么0x2EDA就应该是文件长度了吧。往后翻11994个字节,文件果然在那里结束。(可以用Shift+PageDown或Shift+方向键来实现选中一段数据,此时UltraEdit右下角会显示选中的数据的字节数。)

我们可以把这一段数据复制出来,新建一个文件,粘贴进去,保存下来。嗯,这是一个文本文件,里面是一些脚本。当然,里面的内容和本文无关……
需要注意的是,UltraEdit有一个由来已久的Bug,就是新文件直接粘贴二进制数据之后所有的00会被替换成20,因此需要按Ctrl+H之后全选再粘贴一次。当然,现在这个文本文件中不存在00字节。

文件位置和文件大小都找到了,这个例子是否结束了呢?没有。
下面还有几个问题:
(1)这个文件长度和文件大小是对应前面的那个文件名还是对应后面的那个文件名?
(2)文件夹是怎么表示的?

为了解答这两个问题,让我们回到PCK文件头部。"PARGLOBAL.DAT"前面的八个字节很不正常,文件长度为FF FF FF FF?在有符号32位整数中,这表示-1。再看下一个FF FF FF FF出现的位置,这个-1之前出现的是一个没有扩展名的文件名,应该是一个文件夹。这么说来,文件位置和文件大小是和前面的文件名对应的。那么,一个文件索引应该是40个字节的文件名称+文件长度Int32+文件位置Int32了?

仔细看FF FF FF FF前面,是01 00 00 00。往后查找FF FF FF FF(注意去掉“查找ASCII前面的勾”),可以发现所有的FF FF FF FF前面,只有两种情况,即01 00 00 00和FF 00 00 00,而且01 00 00 00的都有文件名,FF 00 00 00的文件名都为空。因此,这是一个类型标志,00 00 00 00表示正常文件,01 00 00 00表示文件夹。

再回到文件头,发现第一个FF FF FF FF后面的文件位置是0x30,正好是"PARGLOBAL.DAT"索引的开头。第二个FF FF FF FF后面的文件位置0xC0,正好是下面的那个文件"ACTIVADOR.AN2"索引的开头。我们可以推测所有文件夹的位置就是指其中的第一个文件的索引的开头。而文件名的字符串长36字节。另外,开始一直没有提及的开头的"DATA"那一段,现在也可以认为也表示一个文件夹的索引。

那么文件夹中的其他文件怎么表示呢?应该是接着的那个文件吧?嗯……
那么接下去有多少的文件呢?不知道……那么,应该需要一个结束标志吧,刚才那个没有文件名的索引似乎可以看作是结束标志。

这是对的。类型标志的FF 00 00 00表示文件夹结束。

总结一下:

PCK结构


文件索引表

File DB
Name    String 36  文件名
Type    Int32  4   文件是0,文件夹是1,文件夹结束为0xFF
Length  Int32  4   文件夹为0xFFFFFFFF
Address Int32  4   文件数据地址,文件夹为第一个文件的File DB地址

有很多个File DB,其中文件夹由文件夹的File DB和Name为空,Type为0xFF,Length和Address均为0xFFFFFFFF的特殊File DB配对

Data DB
文件数据,以2048字节为最小单位对齐。盟军3这部分经过简单的异或加密


文件夹结构
文件夹配对,文件夹和文件夹结尾以类似多层括号的方式配对。

DATA{

    PARGLOBAL.DAT

    VAR.DAT

    ANIMS{

        ACTIVADOR.AN2

        ...

        ABI{

            ...

        }

    }

    ...

}


其余的就是一些编码工作了……


本节中提到的PCK文件的文件格式表(其中还包含对文章不会提到的盟军敢死队3的文件格式):
http://www.cnblogs.com/Rex/articles/639916.html

盟军敢死队开发工具箱
http://www.cnblogs.com/Rex/archive/2007/02/11/647203.html
其中的PCKManager可以打开盟军敢死队系列的包文件。
源文件参考Src\FileSystem\Comm2\PCK.vb和Src\PCKManager(VB2005)。


(未完待续)

posted @ 2007-12-12 22:51  地狱门神  阅读(3174)  评论(3)    收藏  举报