GPC文件格式分析

作者按:罕徳冈曼先生重温了《剑芒X曼史》这个游戏,感慨万千,老泪纵横。想当年玩仙剑看到白布下的林月如也灭有哭过滴他,却在本游戏中香奈儿死的时候掉泪了……毕竟是前一天晚上才H过的亚……究竟本游戏有灭有可以救香奈儿MM的方法呢?解出图片便知道了……

顺带一提的是,GPC是PC98 HGAME上经常出现的图片格式,IDLES公司常用它作为游戏图片。例如:《Angel》,《亚X子》,《X兽学园》,……

GPC的压缩比并不高,因为它使用游程码的关系。每个包的第一个字节A控制大流程,它的每一位以8个字节为单位指出非0的包。从高位开始,为0表示有8个全0字节,为1表示要进一步观察。为0时,简单填充8个字节即可;当为1时,下一个字节B指出本包中8个字节是否为0。字节B从高位开始,为0表示一个0字节,为1表示从流中读一个作为目标字节。以此类推。

在解完一行后,要对解开的字节进行异或的解密。具体解密过程请见代码。

调色板是2个字节表示一个颜色(相当粗略啊^^)

另外要注意的是GPC有隔行扫描方式,在第10h字节指出共有几次扫描。如只有1次,就直接从第0行到最后一行。如有2次,则是0,2,4,6...n,1,3,5,...n-1这样的组织。以次类推。

下面给出GPC转BMP的代码,在VS2003上编译通过。使用方法是gpc2bmp [文件名],文件名表示要转化的gpc文件,可包含通配符。缺省为*.GPC。

// gpc2bmp.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include "windows.h"
#include "io.h"

#define GPC_EOF  -1

struct gpc_buf {
 unsigned char *buf;
 unsigned char *org_buf;
 unsigned char *buf_limit;
 int width, height;
};

int gpc_next_byte(struct gpc_buf* data);
int gpc_next_word(struct gpc_buf* data);
void gpc_seek(struct gpc_buf* data, int offset);

BOOL gpc_load(struct gpc_buf* data, const char * filename);
void gpc_free(struct gpc_buf* data);

int gpc_next_byte(struct gpc_buf* data)
{
 if ( data->buf >= data->buf_limit ) {
  return GPC_EOF;
 }
 return *data->buf++;
}

int gpc_next_word(struct gpc_buf* data)
{
 if ( data->buf >= data->buf_limit ) return GPC_EOF;
 int ret = *(unsigned short *)data->buf;
 data->buf += 2;
 return ret;
}

BOOL gpc_load(struct gpc_buf* data, const char * filename)
{
 FILE *fp = fopen(filename, "rb");
 if ( !fp ) return FALSE;
 fseek(fp, 0, SEEK_END);
 int len = (int)ftell(fp);
 fseek(fp, 0, SEEK_SET);
 data->buf = (unsigned char*)malloc(len); 
 data->buf_limit = data->buf + len;
 data->org_buf = data->buf;
 fread(data->buf, len, 1, fp);
 fclose(fp);
 return TRUE;
}

void gpc_free(struct gpc_buf* data)
{
 if (!data->org_buf) return;
 free(data->org_buf);
 data->org_buf = NULL;
}

void gpc_seek(struct gpc_buf* data, int offset)
{
 data->buf = data->org_buf + offset;
}

void gpc2bmp(struct gpc_buf* data, const char* target_file)
{
 gpc_seek(data, 0x10);
 int scans = gpc_next_word(data);
 gpc_seek(data, 0x14);
 int palette_offset = gpc_next_word(data);
 gpc_seek(data, 0x18);
 int image_offset = gpc_next_word(data);

 gpc_seek(data, image_offset);
 int width = gpc_next_word(data);
 int height = gpc_next_word(data);

 int bpl = (width + 7) / 8;
 bpl *= 4;
 data->width = width;
 data->height = height;

 RGBQUAD palette[16];
 gpc_seek(data, palette_offset);
 int colorcount = gpc_next_word(data); // ignore it
 int transcolor = gpc_next_word(data); // ignore it
 for ( int i = 0; i < 16; ++i ) {
  int paldata = gpc_next_word(data);
  palette[i].rgbGreen = (paldata >> 8) & 0xf;
  palette[i].rgbRed = (paldata >> 4) & 0xf;
  palette[i].rgbBlue = (paldata) & 0xf;
  palette[i].rgbReserved = 0;
  palette[i].rgbRed <<= 4;
  palette[i].rgbGreen <<= 4;
  palette[i].rgbBlue <<= 4;
 }
 unsigned char *bits_buffer = (unsigned char *)malloc(bpl * height);
 gpc_seek(data, image_offset + 0x10);
 unsigned char decode_buf[1024];
 int line_words = (bpl + 1) / 2;
 unsigned char *next_line_pos = decode_buf + line_words * 2 + 1;
 unsigned char *p_target = decode_buf;
 unsigned char *disp_buf = decode_buf + 0x180;
 memset( disp_buf, 0, line_words * 2 );
 memset( bits_buffer, 0, bpl * height );

 for ( int s = 0; s < scans; ++ s ) {
  for ( int i = s; i < height; ) {

   unsigned char *p = p_target;
   while ( p < next_line_pos ) {
    int ch = gpc_next_byte(data);
    for ( int j = 0; j < 8; ++j, ch <<= 1 ) {
     if ( ch & 0x80 ) {
      // needs more
      int ch2 = gpc_next_byte(data);
      for ( int k = 0; k < 8; ++k, ch2 <<= 1 ) {
       if ( ch2 & 0x80 ) {
        // load a byte
        *p++ = gpc_next_byte(data);
       } else {
        // a zero byte
        *p++ = 0;
       }
      }
     } else {
      // 8 bytes of empty!
      for ( int k = 0; k < 8; ++k ) {
       *p++ = 0;
      }
     }
    }
   }
   p_target = p;
   // post_decode_1
   int ch = decode_buf[0];
   if ( ch != 0 ) {
    unsigned char ch1 = decode_buf[1];
    unsigned char *start = &decode_buf[1];
    unsigned char *p = start + ch;
    for ( int j = ch; j > 0; --j ) {
     while ( p < next_line_pos ) {
      ch1 ^= *p;
      *p = ch1;
      p += ch;
     }
     start++;
     p = start;
    }
   }
   // post decode 2
   unsigned char *src = decode_buf + 1;
   unsigned char *dest = disp_buf;
   for ( int j = 0; j < line_words * 2; ++j ) {
    *dest ^= *src;
    src++;
    dest++;
   };
   // post decode 3
   int size = (int)(p_target - next_line_pos);
   memcpy( decode_buf, next_line_pos, p_target - next_line_pos );
   p_target = decode_buf + (p_target - next_line_pos);
   src = disp_buf;
   // 4 planes -> b4b4
   // a0 a1 a2 a3 a4 a5 a6 a7
   // b0 b1 b2 b3 b4 b5 b6 b7
   // c0 c1 c2 c3 c4 c5 c6 c7
   // d0 d1 d2 d3 d4 d5 d6 d7
   // ==>
   // a0b0c0d0 a1b1c1d1 ; a2b2c2d2 a3b3c3d3 ; a4b4c4d4 a5b5c5d5 ; a6b6c6d6 a7b7c7d7
   unsigned char bits;
   for ( int j = 0; j < 4; ++j ) {
    dest = bits_buffer + (height - i - 1) * bpl;
    for ( int k = 0; k < line_words / 2; ++k ) {
     bits = *src++;
     if (bits & 0x80) dest[0] |= 0x10 << j;
     if (bits & 0x40) dest[0] |= 0x01 << j;
     if (bits & 0x20) dest[1] |= 0x10 << j;
     if (bits & 0x10) dest[1] |= 0x01 << j;
     if (bits & 0x08) dest[2] |= 0x10 << j;
     if (bits & 0x04) dest[2] |= 0x01 << j;
     if (bits & 0x02) dest[3] |= 0x10 << j;
     if (bits & 0x01) dest[3] |= 0x01 << j;
     dest += 4;
    }
   }
   i += scans;
  }
 }

 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 = bpl * height;
 bmi.biWidth = width;
 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);
 fwrite(palette, 4, 16, fp);
 fwrite(bits_buffer, 1, bpl * height, fp);
 fclose(fp);
 free(bits_buffer);
}

int _tmain(int argc, _TCHAR* argv[])
{
 char *input_files = "*.gpc";
 if ( argc == 2 ) {
  input_files = argv[1];
 }
 if ( argc > 2 ) {
  printf("usage: gpc2bmp [gpc 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 gpc_buf data;
   printf("Processing %s ...", t.name);
   gpc_load( &data, t.name );
   gpc2bmp( &data, target_file );
   gpc_free( &data );  
   printf("Done./n");
  }while(_findnext(handle, &t) != -1);
 }

 return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值