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[i] = 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[i] = 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[i].rgbRed = palette[i * 3];
  bmp_pal[i].rgbGreen = palette[i * 3 + 1];
  bmp_pal[i].rgbBlue = palette[i * 3 + 2];
  bmp_pal[i].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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值