植物大戰殭屍資原始檔提取 總結
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 = '\\';
}