PvZ资源文件提取 总结
参考
- 植物大战僵尸资源文件(main.pak):格式/提取/工具下载[转贴]
- 植物大战僵尸pak资源文件分析
- 植物大战僵尸资源提取
- C/C++中判断某一文件或目录是否存在
- PNG文件格式详解
- C语言创建文件夹
- Big Endian 和 Little Endian 详解
资源文件格式
资源文件名字为main.pak
,与主程序PlantsVsZombies.exe
位于同一目录下,遵循以下加密方式:资源文件分为两个部分,前一部分记录了每一个资源(包括图片、音乐)的名称长度、文件名称(包括所储存的目录)、大小以及其他的标识符,在这里称其为每个文件的信息块,后一部分是文件的数据内容,前一部分的文件顺序与后一部分的数据顺序相同,在这里称其为数据块。如果前一部分的文件的信息块的排布方式是:
A.lengthOfName A.name A.size
B.lengthOfName B.name B.size
C.lengthOfName C.name C.size
...
[仅仅作为示例,不代表真实的信息块数据]
那么后一部分的数据区的文件的数据块的排布顺序与之相同:
A.data
B.data
C.data
...
虽然数据区的文件的数据块都是紧密相连没有空隙的,但是通过前一部分的信息,可以精确地把每一个文件提取出来,并且保持原有的相对目录结构:
/compiled/particles/Award.xml.compiled
/compiled/particles/AwardPickupArrow.xml.compiled
[省略若干文件]
/compiled/particles/Zombie_seaweed.xml.compiled
/compiled/reanim/Blover.reanim.compiled
/compiled/reanim/Cabbagepult.reanim.compiled
[省略若干文件]
/compiled/reanim/Zombie_zamboni.reanim.compiled
/data/BrianneTod12.txt
/data/BrianneTod16.txt
[省略若干文件]
/data/_Pix118Bold.gif
/images/Almanac.png
/images/Almanac_CloseButton.png
[省略若干文件]
/images/Zombie_digger_dirt.png
/particles/AwardGlow.png
/particles/AwardPickupGlow.png
[省略若干文件]
/particles/Zomboss_particles.png
/properties/LawnStrings.txt
/properties/resources.xml
/reanim/AC_RunActiveContent.js
/reanim/anim_sprout.png
[省略若干文件]
/reanim/Zombie_zamboni_wires.png
/sounds
/sounds/awooga.ogg
/sounds/ballooninflate.ogg
[省略若干文件]
/sounds/zombie_falling_2.ogg
/tmp.txt
幻数 Magic number
在资源文件的最开始的9个字节是幻数,用于标记文件,不包含实际的文件数据。
信息块
文件的信息块在资源文件的前一个部分依次排开,每一个信息块包含如下信息:
顺序 | 字长 | 含义 |
---|---|---|
1 | 1 byte | 文件名的长度,注意,文件名不包含**\0** |
2 | x byte | 文件名,长度已给出 |
3 | 4 byte | 对应数据块的长度 |
4 | 8 byte | 无含义 |
5 | 1 byte | 结束字符,如果是0x00 则表示一个信息块的结束,如果是0x80 则表示前一部分结束 |
数据块
由于每一个信息块记录了文件的名称等信息,所以可以根据这些信息将资源文件重新构建出来,例如,第一个文件的信息块内容如下:
25
636f 6d70 696c 6564 5c70 6172 7469 636c 6573 5c41 7761 7264 2e78 6d6c 2e63 6f6d 7069 6c65 64
0504 0000
8543 cfcc 0efc cb01
00
这里已经将信息块的内容按照上表进行了分割,第一行对应文件名的长度,即37(十六进制下25对应的十进制数为37),得出第一个文件名长为37,将后续的37 bytes长度的数据读取出来得到文件名为
compiled\particles\Award.xml.compiled
注意这里包含了目录信息,说明这个文件处在目录下compiled\particles\
,文件名字为Award.xml.compiled
。
后续的8 bytes指明了文件的长度,要注意的的是这里的存储机制是低位编址(little endian),即处在某个数低位的数据储存在地址较小的内存地址中,对应到上述的例子0504 0000,其对应的真正的十六进制数应该是0000 0405,转换为十进制则是1029.
那么后一部分(数据区)的前1029个字节就属于第一个文件,这个文件的名字是Award.xml.compiled
,且位于当前目录下的compiled\particles\
中
对于后续的文件同理。
xor
但是如果我们直接这样处理这个文件,会发现并不能提取出想要的资源文件,那是因为在上述格式的基础上main.pak
还进行了异或加密,即对原有每一个字节与数字0xF7
进行异或运算,而解密只要再将每一位与0xF7
进行一次异或运算即可。
源代码
**xor.c ** 进行异或解密
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
typedef unsigned char byte;
int main(void)
{
FILE *r = fopen("main.pak", "rb");
FILE *w = fopen("main.de.pak", "wb");
if (r == NULL || w == NULL)
{
fprintf(stderr, "Fail to open the file...");
exit(0);
}
else
{
byte tmp = 0;
while (feof(r) == 0)
{
fread(&tmp, 1, 1, r);
tmp = tmp ^ 0xf7;
fwrite(&tmp, 1, 1, w);
}
fclose(w), fclose(w);
fprintf(stdout, "Completed...");
}
return 0;
}
dec.c 按照既定格式提取文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <direct.h>
typedef struct struct_file
{
char *name;
int length;
struct struct_file* next;
} file;
typedef unsigned char byte;
void structwrite(file *, FILE *); // write the data specified by the struct into the file
void preppath(char * const); // to check if the directory exists, if not, it creates one
int main(void)
{
FILE *fp = fopen("main.de.pak", "rb");
if (fp == NULL)
fprintf(stderr, "Fail to open main.de.pak...");
else
{
puts("Scanning...");
file head = {0, 0, NULL}, *tmp = &head;
int flag = 0, len = 0; // length of filename
fseek(fp, 9, SEEK_CUR);
while (1)
{
file* file_tmp = malloc(sizeof(file));
fread(&len, 1, 1, fp); // get length of filename
file_tmp->name = malloc(len + 1); // create a space for storing filename
memset(file_tmp->name, 0, len + 1); // set all the bytes in name space 0
fread(file_tmp->name, 1, len, fp); // read in the name
file_tmp->length = 0;
fread(&(file_tmp->length), 1, 4, fp); // length of file
file_tmp->next = NULL;
tmp->next = file_tmp;
tmp = file_tmp; // maintain the chain table
fseek(fp, 8, SEEK_CUR); // 0x80 for the end of the head struct
fread(&flag, 1, 1, fp);
if (flag == 0x80)
break;
flag = 0, len = 0;
}
puts("Writing...");
tmp = head.next;
while (tmp != NULL)
{
structwrite(tmp, fp);
tmp = tmp->next;
}
fclose(fp);
}
return 0;
}
void structwrite(file * filefp, FILE *datafp)
{
preppath(filefp->name);
FILE *tfp = fopen(filefp->name, "wb"); // open the file for writing
byte *buffer = malloc(filefp->length); // create the space for the content of the file
fread(buffer, 1, filefp->length, datafp); // read the content from the origin pointer
fwrite(buffer, 1, filefp->length, tfp); // write the content to the target file
fclose(tfp);
}
void preppath(char * path)
{
char *end = path;
while (*end != '\0') end++;
while (*end != '\\') end--;
char *tmp = path;
while (tmp != end)
{
if (*tmp == '\\')
{
*tmp = '\0';
if (-1 == _access(path, 0))
_mkdir(path);
*tmp = '\\';
}
tmp++;
}
*end = '\0';
_mkdir(path);
*end = '\\';
}