1. 程式人生 > 其它 >植物大戰殭屍資原始檔提取 總結

植物大戰殭屍資原始檔提取 總結

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個位元組是幻數,用於標記檔案,不包含實際的檔案資料。

資訊塊

檔案的資訊塊在資原始檔的前一個部分依次排開,每一個資訊塊包含如下資訊:

順序字長含義
11 byte檔名的長度,注意,檔名不包含**\0**
2x byte檔名,長度已給出
34 byte對應資料塊的長度
48 byte無含義
51 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 = '\\';
}