提取工具的代码我放到github上了:https://github.com/langresser/dnfextrator

虽然上文已经有比较详尽的分析了,但是真正实现好一个资源提取工具还是花了我两天的时间。这里把需要注意的地方记录下来。

1、npk包的格式:

struct NPK_Header
{
    char flag[16]; // 文件标识 "NeoplePack_Bill"
    int count;     // 包内文件的数目
};

struct NPK_Index
{
    unsigned int offset;  // 文件的包内偏移量
    unsigned int size;    // 文件的大小
    char name[256];// 文件名
};

char decord_flag[256] = "puchikon@neople dungeon and fighter DNF";



解npk包非常好处理,读取完NPK_Header紧接着根据里面的count数目循环读取NPK_Index,读取完毕后,就可以根据里面的offset定位到指定位置读取img文件。现在的dnf包npk包内的文件名是加密过的,要用decord_flag异或NPK_Index中的name才能获取实际文件名。decord_flag总共有256个字节,剩余部分用"DNF"三个字母填满,最后一个字节置0。读取文件名时可以像这样解密:


char temp[256] = {0};
fread(temp, 256, 1, fp);
for (int i = 0; i < 256; ++i) {
	index.name[i] = temp[i] ^ decord_flag[i];
}



2、img文件格式:


struct NImgF_Header
{
	char flag[16]; // 文件标石"Neople Img File"
	int index_size;	// 索引表大小,以字节为单位
	int unknown1;
	int unknown2;
	int index_count;// 索引表数目
};

struct NImgF_Index
{
	unsigned int dwType; //目前已知的类型有 0x0E(1555格式) 0x0F(4444格式) 0x10(8888格式) 0x11(不包含任何数据,可能是指内容同上一帧)
	unsigned int dwCompress; // 目前已知的类型有 0x06(zlib压缩) 0x05(未压缩)
	int width;        // 宽度
	int height;       // 高度
	int size;         // 压缩时size为压缩后大小,未压缩时size为转换成8888格式时占用的内存大小
	int key_x;        // X关键点,当前图片在整图中的X坐标
	int key_y;        // Y关键点,当前图片在整图中的Y坐标
	int max_width;    // 整图的宽度
	int max_height;   // 整图的高度,有此数据是为了对齐精灵
};



img文件也是一系列图片的合集,它里面还包含很多有用的信息,比如图片的坐标(用于对齐),这个数据是我们想正常使用这个图片所必须的。也正是由于现有的工具都没有提供方便的批量导出和该数据的处理功能,我才想自己写个提取工具的。



img文件是由一个header+多个连续的索引表+实际图片数据组成的。读取图片数据需要跳过header(固定大小)和索引表(header.index_size标识)。


我们读取的文件大小是由NImgF_Index.size决定的,如果dwCompress为6则表示图片有经过zlib压缩,这时size表示压缩后大小。如果为5表示没有压缩,这时size表示转换成8888格式所占内存大小(也就是说,如果dwType为0x0e或是0x0f,size要除2)


如果有压缩,需要zlib解压:


int ret = uncompress(temp_zlib_data, &zlib_len, temp_file_data, size);

注意,temp_zlib_data是一个足够大的缓存区,zlib_len传入的是缓存区的大小。



读取完的数据是图片像素数据,接下来要写入到png图片中(看个人需要bmp什么的也可以)


libpng的使用(包含颜色格式之间的转换代码):


FILE *fp = fopen(file_name, "wb");  
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);  

info_ptr = png_create_info_struct(png_ptr);   
    if (setjmp(png_jmpbuf(png_ptr)))  
    {  
        printf("[write_png_file] Error during init_io");  
        return;  
    }  
    png_init_io(png_ptr, fp);  

    /* write header */  
    if (setjmp(png_jmpbuf(png_ptr)))  
    {  
        printf("[write_png_file] Error during writing header");  
        return;  
    }  

    png_set_IHDR(png_ptr, info_ptr, width, height,  
        8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,  
        PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);  
  
    png_write_info(png_ptr, info_ptr);  
  
    /* write bytes */  
    if (setjmp(png_jmpbuf(png_ptr)))  
    {  
        printf("[write_png_file] Error during writing bytes");  
        return;  
    }

    row_pointers = (png_bytep*)malloc(height*sizeof(png_bytep));  
    for(int i = 0; i < height; i++)  
    {  
        row_pointers[i] = (png_bytep)malloc(sizeof(unsigned char)* 4 * width);  
        for(int j = 0; j < width; ++j)  
        {  
           
			// png is rgba
			switch (type)
			{
				case ARGB_1555://1555
					row_pointers[i][j * 4 + 0] = ((data[i * width * 2 + j * 2 + 1] & 127) >> 2) << 3;   // red  
					row_pointers[i][j * 4 + 1] = (((data[i * width * 2 + j * 2 + 1] & 0x0003) << 3) | ((data[i * width * 2 + j * 2] >> 5) & 0x0007)) << 3; // green  
					row_pointers[i][j * 4 + 2] = (data[i * width * 2 + j * 2] & 0x003f) << 3; // blue 
					row_pointers[i][j * 4 + 3] = (data[i * width * 2 + j * 2 + 1] >> 7) == 0 ? 0 : 255; // alpha
					break;
				case ARGB_4444://4444
					row_pointers[i][j * 4 + 0] = (data[i * width * 2 + j * 2 + 1] & 0x0f) << 4;   // red  
					row_pointers[i][j * 4 + 1] = ((data[i * width * 2 + j * 2 + 0] & 0xf0) >> 4) << 4; // green  
					row_pointers[i][j * 4 + 2] = (data[i * width * 2 + j * 2 + 0] & 0x0f) << 4;; // blue  
					row_pointers[i][j * 4 + 3] = ((data[i * width * 2 + j * 2 + 1] & 0xf0) >> 4) << 4; // alpha
					break;
				case ARGB_8888://8888
					row_pointers[i][j * 4 + 0] = data[i * width * 4 + j * 4 + 2]; // red
					row_pointers[i][j * 4 + 1] = data[i * width * 4 + j * 4 + 1]; // green
					row_pointers[i][j * 4 + 2] = data[i * width * 4 + j * 4 + 0]; // blue
					row_pointers[i][j * 4 + 3] = data[i * width * 4 + j * 4 + 3]; // alpha
					break;
				case ARGB_NONE:// 占位,无图片资源
					break;
				default:
					printf("error known type:%d\n", type);
					break;
			}
        }  
    }  
    png_write_image(png_ptr, row_pointers);  
  
    /* end write */  
    if (setjmp(png_jmpbuf(png_ptr))) {
        printf("[write_png_file] Error during end of write");  
        return;  
    }  
    png_write_end(png_ptr, NULL);
// 别忘记释放内存
    png_destroy_write_struct(&png_ptr, &info_ptr);
  
    /* cleanup heap allocation */  
    for (int j=0; j < height; j++)  
        free(row_pointers[j]);  
    free(row_pointers);  
  
    fclose(fp);