GRA文件格式分析
笔者按:某日某人 疯狂的迷上了PC98模拟器上一个叫mari的游戏.这个游戏难得可以用BT来形容了.但是丝毫没有动摇此人通关此GAME的决心."通关了就好了..通关了就一定有回想模式的..."他对自己说.可惜,回想模式只是一些无聊的黑白图片,完全没有此人想象中的H图片秀。。。。此人发现,此GAME文件夹下有很多.GRA的文件。。。和之前玩的《迷走X市》以及《天使之X后》一样...
本文介绍的GRA图片格式仅限于16色。
首先是可忽略的项目,直到遇到0x1a。0x1a即Ctrl^Z。因此这些项目可以是一段文本。
然后是一个字节的0。
然后是flag字节。此字节最高位为1表示文件内无内置调色板,为0表示有调色板。如果为0,则在图像数据之前会有48个字节的调色板数据,按RGB顺序。本文没有处理无调色板的情况。
然后是一个word,此word跟rom打交道,忽略之。注意:GRA文件中出现的所有word都是big-endian的,即高字节在前低字节在后。
然后是一个注释块。此块以一个大小字节开始,后跟注释块内容。大小字节必须是4。
然后又是一个注释块,此块以一个word指出大小,后跟内容。
然后是宽度1个word,高度1个word。
接着是调色板数据。紧接着是图片数据。
图片数据是高度压缩的,最终可达到约3:1的无损压缩比。它的机理为:游程码+固定码表huffman编码+字典表。
字典表是一个256字节的表,它开始被这样初始化:
0, f0, e0, ... 10,
10, 00, f0, .. 20,
...
f0, e0, d0, ... 00
huffman+字典表以2个点为单位,并且后面的点将成为前面的点的字典偏移选择,例如:解开一个f0,则后面一个点查表时从f0开始,以f0, e0, d0, ...00这一段为表。同时,每次从字典查到一个码后,都要将它提到本段最前。字典以16个字节为单位。huffman编码给出的是查表的位数n,之后为n位的表偏移,例如位数为1,之后的1bit为1,则表示取该段的第1个字节f0,并把第1个字节提到最前,把第0字节推后。
huffman表:
code rewind count
11 (91c7) 1(swap top 2 bytes)
10 0
00 2+nextbit
010 4+nextbit*2+nextbit
011 8+nextbit*4+nextbit*2+nextbit
数据的组织基本安排如下:
首先用1个哈夫曼的数据填充一行,然后游程码给出要拷贝的偏移(负数,可以是4,width/2,width/2-1,...),或者不拷贝,直接取后面n个编码。具体见本文代码。
以下是从gra转化为bmp的源码,运行方式是在命令行下直接打:gra2bmp gra文件名或通配符(缺省为*.gra)。代码在vs2003中编译通过。
// gra2bmp.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "windows.h"
#include "io.h"
struct gra_data {
unsigned char *buf;
unsigned char *org_buf;
int count;
unsigned char byte_left;
unsigned char left_bit_count;
};
int load_gra_data(char* filename, struct gra_data* pdata)
{
FILE *fp = fopen(filename, "rb");
if ( !fp ) return 0;
fseek(fp, 0, SEEK_END);
int len = (int)ftell(fp);
fseek(fp, 0, SEEK_SET);
pdata->buf = (unsigned char*)malloc(len);
pdata->count = len;
pdata->left_bit_count = 0;
pdata->byte_left = 0;
pdata->org_buf = pdata->buf;
fread(pdata->buf, len, 1, fp);
fclose(fp);
return 0;
}
void free_gra_data(struct gra_data* pdata)
{
if ( !pdata->org_buf ) return;
free(pdata->org_buf);
pdata->org_buf = NULL;
}
#define GRA_EOF -1
int gra_next_bit(struct gra_data *pdata)
{
if ( pdata->left_bit_count == 0 ) {
if ( pdata->count <= 0 ) return GRA_EOF;
pdata->left_bit_count = 8;
pdata->byte_left = *pdata->buf++;
pdata->count--;
}
int ret;
if (pdata->byte_left & 0x80) ret = 1; else ret = 0;
pdata->left_bit_count--;
pdata->byte_left <<= 1;
return ret;
}
unsigned char gra_next_byte(struct gra_data *pdata)
{
unsigned char ch = *pdata->buf++;
pdata->count--;
return ch;
}
void gra_ignore_bytes(struct gra_data *pdata, int n)
{
pdata->buf += n;
pdata->count -= n;
}
BOOL gra_eof(struct gra_data *pdata)
{
return pdata->count <= 0;
}
void init_dict(unsigned char *dict)
{
unsigned char ch;
int j;
for ( j = 0, ch = 0; j < 16; j++, ch += 16 ) {
for ( int i = 0; i < 16; ++i, ch -= 16 ) {
*dict++ = ch;
}
}
}
unsigned char swap_dict(unsigned char *dict, int lastindex)
{
if ( lastindex == 0 ) return dict[0];
if ( lastindex == 1 ) {
unsigned char ch = dict[1];
dict[1] = dict[0];
dict[0] = ch;
return ch;
}
unsigned char ch = dict[lastindex];
for ( int i = lastindex; i > 0; --i ) {
dict = dict[i - 1];
}
dict[0] = ch;
return ch;
}
int decode_code(struct gra_data* pdata, unsigned char *dict, unsigned char dict_offset)
{
char buf[40];
sprintf( buf, "%02x\n", dict_offset );
//OutputDebugString( buf );
int bit = gra_next_bit(pdata);
bit = (bit << 1) | gra_next_bit(pdata);
int ch1, ch2, cnt;
switch(bit)
{
case 3: // 11
ch1 = swap_dict(dict + dict_offset, 1);
break;
case 2: // 10
ch1 = dict[dict_offset];
break;
case 0: // 00
cnt = 2 + gra_next_bit(pdata);
ch1 = swap_dict(dict + dict_offset, cnt);
break;
default: // 01
if ( gra_next_bit(pdata) == 0 ) {
// 010
cnt = 2 + gra_next_bit(pdata);
cnt = (cnt << 1) + gra_next_bit(pdata);
} else {
// 011
cnt = 2 + gra_next_bit(pdata);
cnt = (cnt << 1) + gra_next_bit(pdata);
cnt = (cnt << 1) + gra_next_bit(pdata);
}
ch1 = swap_dict(dict + dict_offset, cnt);
break;
}
dict_offset = ch1;
bit = gra_next_bit(pdata);
bit = (bit << 1) | gra_next_bit(pdata);
switch(bit)
{
case 3: // 11
ch2 = swap_dict(dict + dict_offset, 1);
break;
case 2: // 10
ch2 = dict[dict_offset];
break;
case 0: // 00
cnt = 2 + gra_next_bit(pdata);
ch2 = swap_dict(dict + dict_offset, cnt);
break;
default: // 01
if ( gra_next_bit(pdata) == 0 ) {
// 010
cnt = 2 + gra_next_bit(pdata);
cnt = (cnt << 1) + gra_next_bit(pdata);
} else {
// 011
cnt = 2 + gra_next_bit(pdata);
cnt = (cnt << 1) + gra_next_bit(pdata);
cnt = (cnt << 1) + gra_next_bit(pdata);
}
ch2 = swap_dict(dict + dict_offset, cnt);
break;
}
return (ch2 << 8) + ch1;
}
unsigned char *decode_until_bit0(struct gra_data* pdata,
unsigned char *dest,
unsigned char *dict,
int* old_offset)
{
int code;
int dict_offset = *(dest - 1);
for ( ;; ) {
code = decode_code(pdata, dict, dict_offset);
*(unsigned short *)dest = (short)code;
dict_offset = code >> 8;
dest += 2;
if ( gra_next_bit(pdata) != 1 ) break;
}
*old_offset = 0;
return dest;
}
unsigned char *get_n_bytes_and_copy(struct gra_data* pdata,
unsigned char *dest,
unsigned char *dict,
int src_offset,
int *old_offset)
{
if ( *old_offset == src_offset ) {
return decode_until_bit0(pdata, dest, dict, old_offset);
}
*old_offset = src_offset;
int bit_cnt = 0, mov_cnt = 0;
if ( gra_next_bit(pdata) == 0 ) bit_cnt = 0;
else {
do {
++bit_cnt;
} while ( gra_next_bit(pdata) == 1 );
}
if ( bit_cnt == 0 ) mov_cnt = 1;
else {
mov_cnt = 1;
for ( int i = 0; i < bit_cnt; ++i ) mov_cnt = (mov_cnt << 1) | gra_next_bit(pdata);
}
for ( int i = 0; i < mov_cnt; ++i ) {
// mov_sw
*(unsigned short *)dest = *(unsigned short *)(dest + src_offset);
dest += 2;
}
return dest;
}
void gra2bmp(struct gra_data* pdata, char* target_file)
{
unsigned char ch;
do {
ch = gra_next_byte(pdata);
if ( gra_eof(pdata) ) return ;
} while ( ch != 0x1A );
gra_next_byte(pdata); // load byte 0
ch = gra_next_byte(pdata); // load byte ignore
unsigned char flags = ch;
gra_next_byte(pdata); // load word(hi)
gra_next_byte(pdata); // load word(lo)
// the word above is related with ROM, just ignore
ch = gra_next_byte(pdata); // 4
if ( ch != 4 ) return ;
gra_ignore_bytes(pdata, 4);
int size = gra_next_byte(pdata);
size = (size << 8) | gra_next_byte(pdata);
gra_ignore_bytes(pdata, size);
int width_bytes;
size = gra_next_byte(pdata);
size = (size << 8) | gra_next_byte(pdata);
width_bytes = size;
size = gra_next_byte(pdata);
size = (size << 8) | gra_next_byte(pdata);
int height = size;
unsigned char palette[48];
if ( flags & 0x80 ) {
// don't know how to fill the palette
} else {
// has palette
for ( int i = 0; i < 48; ++i ) {
palette = gra_next_byte(pdata);
}
}
unsigned char dict[256];
init_dict(dict);
unsigned char *buf = NULL;
buf = (unsigned char*)malloc(width_bytes * height * 2 /* test */);
unsigned char *p = buf;
int code = decode_code(pdata, dict, 0);
// fill background
for ( int i = 0; i < width_bytes; ++i ) {
*(unsigned short *)p = (short)code;
p += 2;
}
int src_offset = 0;
int old_offset = 0;
for ( ;; ) {
if ( gra_eof(pdata) ) break;
if ( gra_next_bit(pdata) == 1 ) {
// 9030
if ( gra_next_bit(pdata) == 0 ) {
src_offset = -width_bytes * 2;
p = get_n_bytes_and_copy(pdata, p, dict, src_offset, &old_offset);
} else {
src_offset = -width_bytes + 1;
if ( gra_next_bit(pdata) == 1 ) src_offset -= 2;
p = get_n_bytes_and_copy(pdata, p, dict, src_offset, &old_offset);
}
} else {
// 90a0
if ( gra_next_bit(pdata) == 1 ) {
src_offset = -width_bytes;
p = get_n_bytes_and_copy(pdata, p, dict, src_offset, &old_offset);
} else {
// 90a7
src_offset = -4;
if ( *(p - 1) == *(p - 2) ) {
// 90fc
if ( src_offset == old_offset ) {
p = decode_until_bit0(pdata, p, dict, &old_offset);
} else {
old_offset = src_offset;
int bit_cnt = 0, mov_cnt = 0;
src_offset = -2;
if ( gra_next_bit(pdata) == 0 ) bit_cnt = 0;
else {
do {
++bit_cnt;
} while ( gra_next_bit(pdata) == 1 );
}
if ( bit_cnt == 0 ) mov_cnt = 1;
else {
mov_cnt = 1;
for ( int i = 0; i < bit_cnt; ++i ) mov_cnt = (mov_cnt << 1) | gra_next_bit(pdata);
}
for ( int i = 0; i < mov_cnt; ++i ) {
// mov_sw
*(unsigned short *)p = *(unsigned short *)(p + src_offset);
p += 2;
}
}
} else {
p = get_n_bytes_and_copy(pdata, p, dict, src_offset, &old_offset);
}
}
}
}
unsigned char *bmp_buffer = NULL;
// 1 byte for 1 pixel, convert to compressed form
int bpl = width_bytes;
// make dword aligned
bpl = width_bytes * 4 + 31;
bpl /= 32; bpl *= 4;
bmp_buffer = (unsigned char *)malloc(width_bytes * height);
for ( int j = 0; j < height; ++j ) {
for ( int i = 0; i < width_bytes / 2; ++i ) {
bmp_buffer[(height - j - 1) * bpl + i] = (buf[j * width_bytes + i * 2]) | (buf[j * width_bytes + i * 2 + 1] >> 4);
}
}
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bmi;
memset(&bfh, 0, sizeof(BITMAPFILEHEADER));
bfh.bfType = 'MB';
bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 64;
memset(&bmi, 0, sizeof(BITMAPINFOHEADER));
bmi.biSize = sizeof(BITMAPINFOHEADER);
bmi.biSizeImage = width_bytes * height;
bmi.biWidth = width_bytes;
bmi.biHeight = height;
bmi.biPlanes = 1;
bmi.biBitCount = 4;
bmi.biClrUsed = 16;
bmi.biClrImportant = 16;
FILE *fp = fopen(target_file, "wb");
fwrite(&bfh, 1, sizeof bfh, fp);
fwrite(&bmi, 1, sizeof bmi, fp);
RGBQUAD bmp_pal[16];
for ( int i = 0; i < 16; ++i ) {
bmp_pal.rgbRed = palette[i * 3];
bmp_pal.rgbGreen = palette[i * 3 + 1];
bmp_pal.rgbBlue = palette[i * 3 + 2];
bmp_pal.rgbReserved = 0;
}
fwrite(bmp_pal, 4, 16, fp);
fwrite(bmp_buffer, 1, bpl * height, fp);
fclose(fp);
free(bmp_buffer);
free(buf);
}
int _tmain(int argc, _TCHAR* argv[])
{
char *input_files = "*.gra";
if ( argc == 2 ) {
input_files = argv[1];
}
if ( argc > 2 ) {
printf("usage: gra2bmp [gra files]\n");
return -1;
}
struct _finddata_t t;
long handle;
if ((handle = (long)_findfirst(input_files, &t)) != -1)
{
char target_file[260];
do
{
strcpy(target_file, t.name);
char *dot_pos = strrchr(target_file, '.');
if ( dot_pos ) *dot_pos = '\0';
strcat(target_file, ".BMP");
struct gra_data data;
printf("Processing %s ...", t.name);
load_gra_data( t.name, &data );
gra2bmp( &data, target_file );
free_gra_data( &data );
printf("Done.\n");
}while(_findnext(handle, &t) != -1);
}
return 0;
}
|