20来年前很喜欢魔力宝贝,最高才到达过40多级。后来陆陆续续玩过怀旧。毕竟游戏过于古老,很多不方便的地方。
4年前想着高清化,只不过领导不支持。官方手游也很不满意。
这一次,准备自己制作一下高清单机版,能够把剧情完整走一遍,也算是完成一个小小的愿望。
非营利项目,代码全部开源。
git地址:https://github.com/mversace/CrossGateRemastered.git
感谢先行者对文件格式的研究。
参考文档:https://tieba.baidu.com/p/976827378?red_tag=0672897547&traceid=
参考文档:https://blog.csdn.net/crossgate7/article/details/82430624
下载SEE4CGW来查看图片,与程序生成的对比,来确定对错
资源分类bin目录
本文解析的是图库类型,配置没什么必要性,在做特效时自行修改就可以了。图库总共7个(有一个重复,实际只有6个)
类型 | 名字 | 对应索引 |
---|---|---|
动画配置 | Anime_3 | AnimeInfo_3 |
动画配置 | Anime_Joy_13 | AnimeInfo_Joy_13 |
动画配置 | AnimeEx_1 | AnimeInfoEx_1 |
动画配置 | AnimeV3_7 | AnimeInfoV3_7 |
动画配置 | Puk2\Anime_PUK2_4 | Puk2\AnimeInfo_PUK2_4 |
动画配置 | Puk3\Anime_PUK3_2 | Puk3\AnimeInfo_PUK3_2 |
??动画配置 | AnimeAp | AnimeApdd |
??战斗调色板 | battle_4 | battletxt_4 |
坐标信息 | coordinatev3_2 | coordinateinfov3_2 |
声音配置 | sound_1 | soundaddr_1 |
图库 | Graphic_20 | GraphicInfo_20 |
图库 | GraphicEx_4 | GraphicInfoEx_4 |
图库 | Graphic_Joy_22 | GraphicInfo_20 |
图库 | GraphicV3_18 | GraphicInfoV3_18 |
图库 | Puk2\Graphic_PUK2_2 | Puk2\GraphicInfo_PUK2_2 |
图库同Puk2 | Puk3\Graphic_PUK2_2 | Puk3\GraphicInfo_PUK2_2 |
图库 | Puk3\Graphic_PUK3_1 | Puk3\GraphicInfo_PUK3_1 |
调色板 | bin\pal\*.cgp | 无 |
调色板格式
调色板文件固定长度708字节,每个颜色3个字节,总共236个颜色。
游戏中0-15号,240-255号颜色有固定的默认值。调色板实际占据的是16-239号
例如图片使用的调色板为16号颜色,也就是对应调色板文件中的0号颜色。取出字节1(Blue)、字节2(Green)、字节3(Red),自行拼配颜色即可。
本程序默认使用palet_08调色板。
索引格式
// 索引文件数据块
struct imgInfoHead
{
unsigned int id;
unsigned int addr; // 在图像文件中的偏移
unsigned int len; // 长度
long xOffset; // 在游戏内的偏移量x
long yOffset; // 在游戏内的偏移量y
unsigned int width;
unsigned int height;
unsigned char tileEast; // 地图上横向几格
unsigned char tileSouth;// 竖向几格
unsigned char flag;
unsigned char unKnow[5];
long tileId; // 所属的地图tile的id
};
索引文件每一张图片包含40个字节的字段。索引文件/40就是总图片数。实际上读取这个索引文件对图片提取没有太大意义,里面的几个字段主要是用于地图的拼接。
图库格式
// 图像bin 文件格式
struct imgData
{
unsigned char cName[2];
unsigned char cVer; // 1压缩
unsigned char cUnknow;
unsigned int width;
unsigned int height;
unsigned int len; // 包含自身头的总长度,后续跟char数组
}; // + char* len = size - 16
每一张图片都有数据头,而且因为是顺序存储,实际上可以不使用索引文件的。
一张完整的图片包含 数据头+图片数据。
cVer说明:
0:未压缩,后续的数据就是图片数据
1:压缩,需要对后续数据进行解压
3:带调色板的压缩。在读取文件头后,还需要再读入4个字节,这4个字节代表调色板解压后的长度。
取图步骤
- 读取索引数据
FILE *pFile = nullptr; std::string strPath = _strPath + "\\bin\\"; if (0 == fopen_s(&pFile, (strPath + strInfo).c_str(), "rb")) { imgInfoHead tHead = { 0 }; int len = sizeof(imgInfoHead); while (len == fread_s(&tHead, len, 1, len, pFile)) _vecImginfo.push_back(tHead); } if (pFile) fclose(pFile);
- 遍历索引读取对应图库数据头
imgData tHead = { 0 }; int len = sizeof(imgData); if (len == fread_s(&tHead, len, 1, len, pFile)) { // 这种是错误的图 if (tHead.width > 5000 || tHead.height > 5000) { saveLog(LOG_ERROR, strErrorFile, strName, "img w or h error", imgHead, tHead); return false; } _cgpLen = 0; // 调色板长度 if (tHead.cVer == 3) { // 多读取4个字节,代表的是调色板的长度 if (4 != fread_s(&_cgpLen, 4, 1, 4, pFile)) { saveLog(LOG_ERROR, strErrorFile, strName, "read cgpLen error", imgHead, tHead); return false; } len += 4; } .... }
- 解密后续数据
if (imgLen == fread_s(_imgEncode, imgLen, 1, imgLen, pFile)) { if (tHead.cVer == 0) { // 未压缩图片 _imgDataIdx = imgLen; memcpy(_imgData, _imgEncode, imgLen); } else if (tHead.cVer == 1 || tHead.cVer == 3) { // 压缩的图片 _imgDataIdx = decodeImgData(_imgEncode, imgLen); if (_imgDataIdx != tHead.width * tHead.height + _cgpLen) { // 这种情况按说是错的 if (_imgDataIdx < tHead.width * tHead.height + _cgpLen) { saveLog(LOG_ERROR, strErrorFile, strName, "decode len more", imgHead, tHead); return false; } else { // 大于的话应该算是不够严谨 saveLog(LOG_INFO, strErrorFile, strName, "decode len less", imgHead, tHead); } } } }
- 填充像素
// 默认使用palet_08.cgp(白天) 调色版 unsigned char *pCgp = _uMapCgp.begin()->second.data(); strCgpName = _uMapCgp.begin()->first; // 使用图片自带调色板 if (_cgpLen > 0 && (int)_imgDataIdx >= w * h + _cgpLen) { pCgp = _imgData + (_imgDataIdx - _cgpLen); strCgpName = "self"; } // 图片数据,竖向方向是反的,从最后一行开始 int imgLen = w * h; for (int i = 0; i < imgLen; ++i) { // 调色板编号 int cIdx = _imgData[i] * 3; int idx = (h - i / w - 1) * w + i % w; _imgPixel[idx] = (pCgp[cIdx]) + (pCgp[cIdx + 1] << 8) + (pCgp[cIdx + 2] << 16); if (pCgp[cIdx] != 0 || pCgp[cIdx + 1] != 0 || pCgp[cIdx + 2] != 0) _imgPixel[idx] |= 0xff000000; }
这里需要注意,存储的图片实际上是从下往上存储的,需要自行修正。
- 生成图片
Gdiplus::Bitmap bmp(w, h, PixelFormat32bppARGB); int idx = 0; for (int row = 0; row < h; ++row) { for (int col = 0; col < w; ++col) { bmp.SetPixel(col, row, p[idx++]); } } CLSID encoderClsid; std::wstring s = L"image/" + wstrExt; if (!GetEncoderClsid(s.c_str(), &encoderClsid)) { return false; } std::wstring sName = wstrName + L"." + wstrExt; bmp.Save(sName.c_str(), &encoderClsid, nullptr);
最终效果
放上我最爱的黄螳螂