植物大战僵尸资源文件提取 总结

PvZ资源文件提取 总结

参考

资源文件格式

资源文件名字为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 = '\\';
}
上一篇:加拿大sakimichan画师146-147期插画设计


下一篇:DAY 147 __doc__/__name__/__file__