Irisfield引擎pac文件封包分析

正所谓考试周除了复习啥都想干,所以分析了一下前几个月比较火的黄油美好国家的构筑方法的pac文件。
(其实这游戏我甚至都没能玩下去,感觉怪怪的)

irisfield是这个黄油社的名字,看起来是自己的引擎,就叫irisfield引擎好了)

Pac文件夹里是打包好的游戏封包文件,我们以Se.pac为例进行一下分析因为比较小
随便掏出一个文件提取工具(这里用的是MultiExtractor),来帮助我们分析一下文件。

很幸运,文件并没有使用压缩或者加密算法,搜索出233个文件,可以看到文件的起始地址和大小

然后就可以正式开始分析文件了,掏出二进制编辑器(这里使用010 Editor),打开文件看一眼。

一眼能看出一堆文件名,选择一下,有256字节长。几个文件名基本紧密连接的,往下拉一下能看到类似数据的东西(上面的文件提取工具结果也可以佐证),所以可以猜测文件结构应该是头部+文件列表+文件数据的形式。

文件提取工具搜索到了233个文件,比对一下,发现文件的第4-7字节(从0开始)的值为233,可以确定这是一个int (小端模式),保存文件数量。再后面的8-11字节,是不是有点眼熟?对,就是第一个文件起始的地址。后面还有4个00,一开始不知道是干啥的,直到看到Game.pac文件有5个G才突然反应过来:文件地址使用64位表示

0-3字节就是传说中的magic number,一开始不知道是啥意思,直到无意间改了编码为Shift-JIS才发现,是日文 ぱく(pac) 的编码,也是绝了

文件名底下显然还有16个字节的迷之数据。拿0110h-010fh来分析,8-11字节 30 28 02 00 看起来显然很熟悉,是第一个文件的文件大小,再看0220h,也就是第二个文件名底下的数据,也出现了 30 28 02 00,猜测是从第一个文件的起始地址开始的文件偏移。但第三个偏移理应是0x022830 + 0x016988 = 0x391B8,但结果并不是,少了8字节。但观察之前的文件搜索结果,确实应该加上这个8。看起来,应该是有一个字节对齐进行了优化。

但最后的4个字节15 00 00 00就很让人迷惑,Se.pac中所有数据都带有一样的数值。一开始是猜测文件类型(wav的类型是0x15),但是观察了其它几个文件并不是。至今也没有得到可靠结果,只能猜测可能是标明该数据在游戏引擎中的类型。

所以就不管了,根据这些信息就可以写出资源解包程序了。
又观察了该社的上一作作品,发现pac文件中使用的是32位偏移地址。好改,改成32位地址就行了。

附C++解包代码如下:

#include <cstdio>
#include <vector>
#include <direct.h>
#include <string>

using namespace std;

#define MAGIC 0xad82cf82
#define BUFFER_SIZE 1024

#define USE_64ADDRESS // 是否使用64位地址

// 8字节对齐

struct header {
    unsigned int magic; // 魔数,为0xad82cf82
    unsigned int resource_count; // 资源文件数
    unsigned long long begin_addr; // 资源文件的开始地址
};

struct resource_info {
    char filepath[256]; // 文件路径
    #ifdef USE_64ADDRESS
    unsigned long long offset; // 地址偏移,文件的实际地址为header.begin_addr + offset
    #else
    unsigned int offset;
    #endif
    unsigned int file_length; // 文件长度
    unsigned int reserved3; // 保留3,猜测可能是文件类型(不是扩展名)之类的
};


int createDirectory(std::string path)
{
    int len = path.length();
    char tmpDirPath[256] = { 0 };
    for (int i = 0; i < len; i++)
    {
        tmpDirPath[i] = path[i];
        if (tmpDirPath[i] == '/')
        {
            if (_access(tmpDirPath, 0) == -1)
            {
                printf("making dir %s\n", tmpDirPath);
                int ret = _mkdir(tmpDirPath);
                if (ret == -1) return ret;
            }
        }
    }
    return 0;
}

void write(FILE* from, FILE* to, int size) {
    int wrote_size = 0;
    char buf[BUFFER_SIZE];

    while(wrote_size < size) {
        int s = size - wrote_size < BUFFER_SIZE ? size - wrote_size : BUFFER_SIZE;
        fread(buf, sizeof(char), s, from);
        fwrite(buf, sizeof(char), s, to);
        wrote_size += s;
    }

}

void writeToFile(char* path, FILE* fp, int size) {
    string str(path), dir;
    {
        int pos = str.find_last_of('/');
        if(pos != -1) {
            dir = str.substr(0, pos + 1);
        } else {
            dir = "";
        }
    }

    if(!dir.empty())
        createDirectory(dir);

    auto f = fopen(path, "wb");
    if(f == nullptr) {
        fprintf(stderr, "error when opening %s for write.\n", path);
        exit(1);
    }

    write(fp, f, size);
    fclose(f);
}

int main(int argc, char* argv[]) {
    if(argc != 2) {
        fprintf(stderr, "argument error!\n");
        return 1;
    }

    auto fp = fopen(argv[1], "rb");
    if (fp == nullptr)
    {
        fprintf(stderr, "open file error!\n");
        return 1;
    }

    header h;
    fread(&h, sizeof(h), 1, fp);

    if(h.magic != MAGIC) {
        fprintf(stderr, "file is not a .pac file!\n");
        fclose(fp);
        return 1;
    }

    printf("resource count: %d, begin at: 0x%x\n", h.resource_count, h.begin_addr);

    vector<resource_info> infos;

    // get resource info
    for(int i; i < h.resource_count; ++i) {
        resource_info ri;
        fread(&ri, sizeof(ri), 1, fp);
        infos.push_back(ri);
        // printf("file path: %s, length: %d, offset: 0x%x\n", ri.filepath, ri.file_length, ri.offset);
    }

    for(auto i : infos) {
        printf("saving %s, size: %d\n", i.filepath, i.file_length);
        _fseeki64(fp, h.begin_addr + i.offset, SEEK_SET); // 挪到文件开头
        writeToFile(i.filepath, fp, i.file_length); // 写入文件
    }

    return 0;
}

打包也好写了,就是要注意对齐,以及解包时要记录那个意义不明的数字。就不附代码了因为没写

第一次研究拆包相关的,还好这个还蛮简单的,就很开心.jpg

1 comment

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据