| 我对金山公司的剑侠情愿系列的图片是仰慕已久啊!最近实在憋不住,我就开始的艰难的图片格式探索.但是在发表这篇文章的中心内容之前,我郑重声明,我对这篇文章所造成的后果不承担任何责任,我是一个游戏的探索者,没有对这种方法和使用这种方法获得的任何数据作任何的商业化.请读者只是将获得的数据用于自己的研究. 通过这种技术,剑侠情缘系列的所有图片就将是垂手可的!但是不要侵犯版权,一切图片都是金山公司所有,自己用吧,不用找美工了!好爽啊!! 好了,言归正传.我们研究的游戏是,剑侠情愿的 demo2 ,研究之后的技术是通用的. 我们来看,在demo2 中有 "Data" 这样一个文件夹,在这里,所有的文件都是 *.dat. 我们知道这里是游戏数据的仓库,那么,他的图片肯定放在这里,看见哪个 graphic.dat 了么?图片肯定就在里面向你傻笑!还有一个叫 gamedata.dat 猜想他里面肯定是 游戏的脚本之类的东东,我们使用一个叫 "WinHex" 的工具软件(好象是黑客 :> ),打开gamedata.dat ,里面是什么?请看下文(别急吗!) 在gamedata.dat 中是游戏的脚本还有一些对话信息,至于是以什么格式存储的,我不多说了.我们的重点是得到图片格式.现在干什么?找啊.我第一个想到的就是 "南宫飞云", 找吧!!找到了好多,注意有一些叫什么 "z02-南宫飞云-sp.mpc",有好多叫什么 *.mpc ,你明白了么?"mpc"肯定就是图片的扩展名。那么接下来呢,我到 graphic.dat 中找 "z02-南宫飞云-sp.mpc",结果,什么都没有,怎么,完蛋了?我猜想肯定文件名被转换成数字,变成编号了,于是呼,我开始疯狂的查找有关与文件名转换成数字的方法,结果,你都猜到了,我被搞的差点仙去!!我想我一辈子也别想知道 西山居是怎么转的,因为随便就能找到一种方法。就这样结束了么? 实在难受,我就随便的看 gamedata.dat 中的内容,突然发现 "MPC File Ver2.0",哦,我知道了,这是mpc的第二版.会不会是一个标记呢?我也写过游戏数据的封装,我的图片标记就是 "清风引擎图片 Ver1.0",每张图片都有,那么!!!哈哈!!! 你知道我要做什么?我到graphic.dat 中查找 "MPC File Ver2.0",谢天谢地,西山居竟然是这样...哈哈...我统计了一下,在graphic.dat 中有 1300多个 "MPC File Ver2.0",啊,我们找了!!!开始破解文件头格式吧!!!也!!! 我们来想想,文件头中有什么,应该是,图片的宽.高.长度.是否压缩.有几张小图片.图片的点是什么格式(8,16,24...),是不是有调色板(游戏引擎自定义的调色板),那么只要找到一个数据来测试,就可以一步一步的揭开,最容易想的就是图片的宽和高.多数是固定的.现在,我们在 gamedata.dat 中找一些关于宽和高的信息,很简单,我们知道了"\mpc\ui\dialog\panel.mpc"的宽是"440",高是"90",都是写英文和中文,你查看gamedata.dat 时就会发现的.我将宽和高转化成16进制的数,凭经验,宽在前,高在后, 查找 " B8 01 00 00 45 00 00 00 "找到了么?你可以发现就一个,说明什么??还有,你看到在 这个数字的前边不远就是 "MPC File ver2.0",哈哈!!!再多查几个,是不是相对位置永远都是那样! 今天接着写.上次说在gamedata.dat 还有 graphic.dat 中查找 mpc...的内容,其实. graphic.dat 中的内容是压缩的(图片头只是压缩了一部分),所以从这两个文件中我们不可能简单的提取出图片信息.但是游戏在运行时肯定要解压,那么图片的信息就应该能够以原型出现在内存中,那么我们就来察看运行时的游戏内存. 使用winhex 的 ram 编辑工具,察看jxqydemo2 的主内存(进程名称为sword). 找到某个 MPC File Ver2.0... 没时间了,以后再写. 想写好一部文章真的挺难。 好了,努力一点。 上回说到使用WinHex查看jxqyDemo2的内存,不知道诸位有否去做了?结果么便是非常的少了,不过总会有点收获吧。前文提到“\mpc\ui\dialog\panel.mpc"的宽是"440",高是"90",都是写英文和中文,你查看gamedata.dat 时就会发现的.我将宽和高转化成16进制的数,凭经验,宽在前,高在后,查找 " B8 01 00 00 45 00 00 00 ”那么,我们就再来找" B8 01 00 00 45 00 00 00 ",结果?!他的上面是不是也有“MPC File Ver2.0”,先不管他。多找几个"MPC File Ver2.0",比如,我知道“剑侠情缘”的最下面的控制面板(就是放物品小图标和武功小图标的大长条),他的宽是"640",高是"69",二进制就是“80 02 00 00 45 00 00 00”,找他!好了这一下就能够确定图片结构中是: 标志 : MPC File Ver2.0 [16字节之后 : xx [这个Int(4字节)是之后的图片数据的长度] 之后 : yy [这个Int 是图片的“最大”宽度] 之后 : zz [这个Int 是图片的"最大"高度] ..... 那么?就这样结束了么??远远没有,好戏还在后头! 今天,我把他的图片格式公布如下。 感兴趣的朋友可以试试。 调色板的结构: struct MPCPALETE { unsigned blue:8; unsigned green:8; unsigned red:8; unsigned alpha:8; //不知道什么作用 } 图片数据的头结构: struct MPCPICDATAHEADER { int picDataSize; int picWidth; int picHeight; int NULL1; // 0 //不知道是什么 int NULL2; // 0 } MPC 图片文件的头结构: struct MPCFILEHEADER { char mpcFlag[16]; // 标志 "MPC File Ver2.0" int mpcNULL1[12]; // 0 int mpcPicDataSize; // 图片数据长度 int mpcMaxWidth; // 所有图片中最大的宽度 int mpcMaxHeight; int mpcPicCount; int mpcPicBitCount; // 8 int mpcPalleteSize; // 调色板数据的长度 int mpcNull3; // 0 } MPC 文件的结构如下: 先是一个头结构:mpcFileheader 接下来是调色板信息:每个颜色32位(MPCPALETE),长度是mpcFileHeader.mpcPalleteSize . 接下来是偏移表,每个偏移量用一个整数(4字节)表示,有多少张图片,就有多少个偏移量。 最后就是图片数据了。 图片数据有一个头结构 ( MPCPICDATAHEADER ),这里的图片的长度不包括这个头结构(重点注意),头结构之后就是压缩后的图片数据了 还有,图片的压缩其实很简单。 比如:(16进制) 84 04 23 45 23 35 .... 那么,他是什么意思呢? 84 ? 先来看 “80”,在二进制中,一个字节最大 “FF” (1111 1111),“80”(1000 0000),其实,“80”,相当与 “-0”,就是负号,那么‘81’,就是“-1”,那么,这个有什么用呢? 你可以看到很多的 “7F”,你问我“为什么你能发现压缩的规律”,其实就是这些分布规则的“7F”告诉我的,你看,"7F"(也就是127),之后恰好有 127 字节,接着又是一个“7F”....,所以我猜测"7F"就是这一块颜色索引的长度,那么,所有的都是么?"84“又是什么呢?你知道,就是 ”-4“ 了,其实啊,就是透明色的个数,当然是连续的透明色的个数。那么”04“? 他啊,就是不透明的连续颜色索引的个数!那么如下的一段压缩码: 84 03 23 23 23 89 06 89 89 89 89 89 89 89 ... ... 是什么意思呢? 有兴趣的朋友可以发贴来猜猜!!! 代码: 我可是从家里电脑抄到纸上,再从纸上抄到这里,大家要珍惜啊。 void savebmp(const char *bmpfileName,unsigned char * bmpData,int nDataLen,int nWidth,int nHeight); void mpc2bmp(const char *mpcFileName) { FILE *mpcfile=NULL; int nbmpFileNameLen=strlen(mpcFileName); char *bmpfileName=new char[nbmpFileNameLen+3]; char *bmpFileName=new char[nbmpFileNameLen+3]; memset(bmpFileName,0,nbmpFileNameLen+3); memset(bmpfileName,0,nbmpFileNameLen+3); strcpy(bmpFileName,mpcFileName); strcat(bmpFileName,"%d.bmp"); nbmpFileNameLen-=4; MPCFILEHEADER mpcFileHeader; MPCPICDATAHEADER mpcPicDataHeader; unsigned char *picData=NULL; unsigned char *bmpData=NULL; int row=0; int col=0; int maxrow=0; int maxcol=0; int nextColorOffset=0; int nextColorOffsetInBmp=0; int npicCount=0; int bmpBufLen=0; int mpcBufLen=0; int i=0; mpcfile=fopen(mpcFileName,"rb"); if(mpcfile==NULL) return; fread(&mpcFileHeader,1,sizeof(mpbFileHeader),mpcfile); MPCPALETE *mpcPalete=new MPCPALETE mpcFileHeader.mpcPalleteSize]; fread(mpcPalete,4,mpcFileHeader.mpcPalleteSize,mpcfile); int *mpcPicOffset=new int[mpcFileHeader.mpcPicCount]; fread(mpcPicOffset=n,1,mpcFileHeader.mpcPicCount*4,mpcfile); fread(&mpcPicDataHeader,1,sizeof(mpcPicDataHeader),mpcfile); mpcBufLen=mpcPicDataHeader.picSize-sizeof(mpcPicDataHeader); picData=new unsigned char[mpcBufLen]; fread(picData,1,mpcBufLen,mpcfile); bmpBufLen=mpcPicDataHeader.picHeight*(mpcPicDataHeader.picWidth*3+mpcPicDataHeader.picWidth%4); bmpData=new unsigned char[bmpBufLen]; maxcol=mpcPicDataHeader.picWidth; maxrow=mpcPicDataHeader.picHeight; while(1) { col =0; row =0; nextColorOffset=0; nextColorOffsetInBmp=0; while(nextColorOffset<mpcBufLen) { if(picData[nextColorOffset]>0x80) { i=picData[nextColorOffset]&0x7F; while(i>0) { bmpData[nextColorOffsetInBmp++]=0xFF; bmpData[nextColorOffsetInBmp++]=0x00; bmpData[nextColorOffsetInBmp++]=0xFF; --i; } col=0; ++row; } ++nextColorOffset; } i=picData[nextColorOffset]; ++nextColorOffset; while(i>0) { bmpData[nextColorOffsetInBmp]= mpcPalete[picData[nextColorOffset]].blue; ++nextColorOffsetInBmp; bmpData[nextColorOffsetInBmp]= mpcPalete[picData[nextColorOffset]].green; ++nextColorOffsetInBmp; bmpData[nextColorOffsetInBmp]= mpcPalete[picData[nextColorOffset]].red; ++nextColorOffsetInBmp; ++nextColorOffset; ++col; ++i; } if(col==maxcol) { for(i=0;i<mpcPicDataHeader.picWidth%4;i++) { bmpData[nextColorOffsetInBmp]=0; ++nextColorOffsetInBmp; } col=0; ++row; } } wsprintf(bmpfileName,bmpFileName,npicCount); savebmp(bmpfileName,bmpData,bmpBufLen, mpcPicDataHeader.picWidth, mpcPicDataHeader.picHeight); delete bmpData; delete picData; ++npicCount; if(npicCount<mpcFileHeader.mpcPicCount) { fread(&mpcPicDataHeader,1,sizeof(mpcPicDataHeader),mpcfile); mpcBufLen=mpcPicDataHeader.picSize- sizeof(mpcPicDataHeader); picData=new unsigned char[mpcBufLen]; bmpBufLen=mpcPicDataHeader.picHeight* mpcPicDataHeader.picWidth*3+ mpcPicDataHeader.picWidth%4); bmpData=new unsigned char[bmpBufLen]; fread(picData,1,mpcbufLen,mpcFile); } else break; } void savebmp(const char *bmpfileName,unsigned char * bmpData,int nDataLen,int nWidth,int nHeight) { FILE *bmpFile=NULL; BITMAPFILEHEADER bmpHeader; BITMAPINFO bmpInfo; unsigned char *tempData; int nPitch=nWidth*3+nWidth%4; tempData=bmpData+nDataLen-nPitch; bmpFile=fopen(bmpfileName,"w+b"); if(bmpFile==NULL) return ; bmpHeader.bfOffBits=54; bmpHeader.bfSize=nDataLen+54; bmpHeader.bfType=19778; //"BM" bmpHeader.bfReserved1=0; bmpHeader.bfReserved2=0; bmpInfo.bmiHeader.biSize=40; bmpInfo.bmiHeader.biWidth=nWidth; bmpInfo.bmiHeader.biHeight=nHeight; bmpInfo.bmiHeader.biPlanes=1; bmpInfo.bmiHeader.biBitCount=24; bmpInfo.bmiHeader.biCompression=0; bmpInfo.bmiHeader.biSizeImage=nDataLen; bmpInfo.bmiHeader.biXPelsPerMeter=3780; bmpInfo.bmiHeader.biYPelsPerMeter=3780; bmpInfo.bmiHeader.biClrUsed=0; bmpInfo.bmiHeader.biClrImportant=0; fwrite(&bmpHeader,1,sizeof(bmpHeader),bmpFile); fwrite(&bmpInfo,1,sizeof(bmpInfo),bmpFile); fseek(bmpFile,(-1)*sizeof(bmpInfo.bmiColors),SEEK_CUR); for(;nHeight>0;--nHeight) { fwrite(tempData,1,nPicth,bmpFile); tempData-=nPitch; } fclose(bmpFile); } |

我靠,你牛。。