魔力宝贝高清单机计划(一) 图库提取

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个字节代表调色板解压后的长度。

取图步骤

  1. 读取索引数据
    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);
    
  2. 遍历索引读取对应图库数据头
    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;
    	}
    	....
    }
    
  3. 解密后续数据
    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);
    			}
    		}
    	}
    }
    
  4. 填充像素
    // 默认使用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;
    }
    

    这里需要注意,存储的图片实际上是从下往上存储的,需要自行修正。

  5. 生成图片
    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);
    

最终效果

放上我最爱的黄螳螂
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_37543025/article/details/88377553