活动介绍

资源分类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); ———————————————— 依照內容生成BIN檔提取圖片工具 魔力宝贝高清单机计划(一) 图库提取 https://blog.csdn.net/qq_37543025/article/details/88377553

时间: 2025-07-25 20:27:41 AIGC 浏览: 24
<think>根据提供的资料,我们需要实现一个从魔力宝贝游戏文件中提取图片的工具。主要步骤包括: 1. 读取索引文件(包含图片信息头) 2. 根据索引文件中的地址,读取图库文件中的图片数据头 3. 根据图片数据头判断图片是否压缩,以及是否包含调色板 4. 解压图片数据(如果需要) 5. 将解压后的数据根据调色板转换为像素颜色 6. 生成图片文件(如PNG) 注意点: - 索引文件中的每个条目是40字节的imgInfoHead结构体。 - 图库文件中的每个图片以一个16字节(或20字节,如果cVer=3)的imgData头开始。 - 调色板文件(.cgp)固定大小为708字节(236个颜色,每个颜色3字节)。 - 图片数据可能是压缩的(cVer为1或3),需要解压。解压算法需要根据资料实现,但资料中没有给出具体算法,需要参考其他资料或原程序。 - 资料中提到,图片数据是竖向反向存储的,即最后一行在数据的最前面。 由于资料中没有提供解压算法的具体实现,这里我们假设有一个decodeImgData函数(需要根据实际游戏文件格式实现,可能是RLE压缩等)。 但根据博客(https://blog.csdn.net/qq_37543025/article/details/88377553)的参考,我们可以知道解压算法可能是简单的RLE压缩。 根据常见的游戏解压,我们可以尝试实现一个简单的RLE解压。但请注意,由于没有具体格式,这里可能需要根据实际文件调整。 步骤: 1. 读取索引文件,得到图片信息头(imgInfoHead)的数组。 2. 打开图库文件,根据索引中的地址(addr)跳转到图片位置。 3. 读取图片数据头(imgData)。 4. 如果cVer为3,则再读取4字节的调色板长度(_cgpLen)。 5. 读取后续的图片数据(长度为imgData.len - 图片头长度)。 6. 如果cVer不为0,则解压数据。 7. 解压后的数据包括图片像素数据(长度为width*height)和可能的调色板数据(如果_cgpLen>0,则位于像素数据之后)。 8. 使用调色板(默认使用外部调色板,如果图片自带调色板则使用自带的)将像素数据转换为ARGB颜色。 9. 创建位图,将颜色数据填充到位图中(注意:行序是从下到上)。 10. 保存为PNG图片。 由于原资料中解压函数decodeImgData没有给出,这里我们假设一个RLE解压算法(类似于魔力宝贝常用的RLE压缩): - 压缩数据由一系列“控制字节”和“数据字节”组成。 - 控制字节的最高位表示是重复还是不重复:如果最高位为1,则低7位表示重复次数(实际次数为低7位+1),下一个字节为重复的像素值;如果最高位为0,则低7位表示后面连续的不重复字节数(实际数量为低7位+1),然后跟着这么多个字节。 但注意:资料中提到解压后的长度应该等于width*height + _cgpLen。如果解压后长度大于这个值,说明有冗余,可以截断;如果小于,则说明错误。 由于原程序是C++,这里我们使用C++实现,并尽量保持原结构。 我们将实现一个类来封装提取过程。 注意:需要处理调色板,默认使用一个外部调色板(例如palet_08.cgp),但如果图片自带调色板(cVer==3),则使用图片自带的。 代码结构: 1. 定义结构体imgInfoHead和imgData(注意字节对齐,使用#pragma pack(1)确保无填充)。 2. 读取索引文件,得到imgInfoHead列表。 3. 读取图库文件,对于每个imgInfoHead,定位到addr,读取imgData头。 4. 根据imgData头读取后续数据,并解压(如果需要)。 5. 转换像素数据。 6. 保存为图片。 由于GDI+在保存图片时需要编码器,我们使用GDI+来生成PNG图片。 环境:需要链接gdiplus.lib,并在程序开始时初始化GDI+。 由于调色板文件(.cgp)需要预先加载,我们可以在程序开始时加载默认调色板(例如bin\pal\palet_08.cgp)。 实现细节: - 图片数据在内存中的排列:解压后,前width*height字节是像素索引,每个字节对应调色板中的一个颜色索引。 - 调色板数据:每个颜色3字节(BGR),注意调色板文件中0号颜色对应游戏调色板的16号颜色(因为游戏中0-15和240-255是固定的,调色板文件只管理16-239号颜色)。但图片自带的调色板数据也是3字节BGR,直接使用。 颜色转换:对于每个像素索引idx(0-255),在调色板中取第idx个颜色(注意:调色板文件中的颜色索引0对应游戏调色板索引16,但图片自带的调色板数据就是完整的调色板?)。实际上,图片自带的调色板数据长度_cgpLen应该是768字节(256个颜色,每个3字节)?但资料中写的是236个颜色(708字节),所以这里可能自带调色板也是236个颜色(16-239)?我们需要验证。 根据资料:调色板文件固定708字节(236个颜色)。所以图片自带的调色板数据长度_cgpLen应该是708。但是,资料中在读取cVer=3时,读取4字节的_cgpLen,然后解压后的数据中,在图片数据后面附加了_cgpLen字节的调色板数据。这个调色板数据应该是708字节。 在转换像素时,对于像素值pixel(0-255): - 如果pixel在0-15或240-255,使用游戏默认颜色(但资料没有给出,所以我们可以忽略,直接使用调色板文件中的颜色?实际上,调色板文件只包含16-239,所以0-15和240-255在调色板文件中不存在?)因此,我们需要一个完整的256个颜色的调色板。 解决方案: - 默认调色板(外部调色板)我们只提供16-239的颜色(708字节),但0-15和240-255的颜色我们使用游戏默认值(资料未提供,所以可能原程序有默认值,但这里我们无法得知)。因此,我们创建一个256*3的调色板数组,其中16-239用调色板文件的数据填充,0-15和240-255用默认值(比如0,或者从其他途径获取,但资料未提供,所以暂时用0)。 - 图片自带的调色板数据(708字节)也是16-239的颜色,同样需要和0-15、240-255拼接。 但资料中提到:图片使用的调色板为16号颜色,也就是对应调色板文件中的0号颜色。所以,在调色板文件中,第0个颜色对应16号,第1个对应17号,...,第235个对应255号?不对,应该是16-239共224个颜色?但236个颜色?16-239是224个,但236>224,所以这里可能是236个颜色覆盖16-251?或者资料有误?我们按照236个颜色覆盖16-251(16+236=252,所以覆盖16-251)?但这样255号颜色就没有了。 重新阅读资料:调色板文件固定长度708字节,每个颜色3个字节,总共236个颜色。游戏中0-15号,240-255号颜色有固定的默认值。调色板实际占据的是16-239号。所以236个颜色就是16-239(16到239一共224个颜色?)236个颜色怎么对应? 计算:236个颜色,那么应该是从16号开始,到16+236-1=251号。所以240-255号颜色还是使用默认值。因此,我们的调色板数组需要256项,其中0-15、240-255用默认值(如果没有默认值,暂时用0),16-251用调色板文件的数据(236个颜色)。 但是,资料中又说:例如图片使用的调色板为16号颜色,也就是对应调色板文件中的0号颜色。所以,调色板文件中的第0个颜色就是游戏调色板的16号颜色,第1个是17号,...,第235个是251号。240-255号颜色还是默认值。 因此,在转换像素时,对于像素值pixel: - 如果pixel在16-251之间,则使用调色板文件中的第(pixel-16)个颜色(3字节BGR)。 - 如果pixel在0-15或252-255,则使用默认颜色(暂时用黑色,但应该用默认值,但资料未提供默认值,所以可能需要从游戏文件中获取,或者保留原样?)。 然而,资料中提供的填充像素代码却是: int cIdx = _imgData[i] * 3; ... pCgp[cIdx] ... 这里将像素值直接乘以3作为调色板的索引,这意味着调色板数组是按0-255的顺序存放的,每个颜色3字节,总共768字节。所以,我们之前对调色板的处理需要调整:我们应该构建一个完整的256个颜色的调色板(每个颜色3字节,共768字节),其中16-251的位置用调色板文件中的236个颜色填充,其他位置用默认值填充。 但是,资料中又说调色板文件只有708字节(236个颜色),所以默认调色板文件不能直接作为768字节的数组使用。因此,我们需要将调色板文件的数据复制到完整调色板的16-251号颜色位置(注意:16号颜色对应调色板文件的第0个颜色)。 同样,图片自带的调色板数据(708字节)也是236个颜色,同样需要复制到完整调色板的16-251号位置。 因此,步骤: - 准备一个256*3=768字节的数组作为完整调色板。 - 初始化0-15号颜色(48字节)为默认值(比如0,或者从其他地方获取,但资料未提供,暂时用0)。 - 初始化240-255号颜色(16*3=48字节)为默认值(同样暂时用0)。 - 将调色板文件(或图片自带的调色板数据)的708字节复制到完整调色板的16号颜色位置(即数组第16*3=48字节处)开始,复制708字节(覆盖16-251号颜色)。 然后,对于每个像素值p(0-255),在完整调色板中取第p个颜色(即从p*3开始的3个字节,顺序为B、G、R)。 资料中的代码也是这么做的:cIdx = _imgData[i] * 3,然后从pCgp(调色板数组)中取cIdx, cIdx+1, cIdx+2。 所以,我们无论使用外部调色板还是图片自带调色板,都需要先构建一个768字节的完整调色板。 但是,资料中提供的代码并没有构建完整调色板,而是直接使用调色板文件数据(708字节)作为调色板数组,这样当像素值大于235时(即大于251号颜色)就会越界。所以,我们需要修改为构建完整调色板。 由于资料中未提供0-15和240-255的默认颜色,我们暂时用黑色(0)填充。如果后续有需要,可以再补充。 另外,资料中在填充像素时,将图片数据竖向反转(因为数据是最后一行在最前面)。 实现步骤: 1. 初始化GDI+。 2. 加载默认调色板文件(如palet_08.cgp)并构建完整调色板(768字节)。 3. 读取索引文件,得到图片信息列表。 4. 打开图库文件,遍历图片信息列表,提取每张图片。 5. 对于每张图片: a. 定位到addr,读取imgData头。 b. 如果cVer==3,再读4字节的_cgpLen(调色板解压后长度,应为708)。 c. 读取后续的压缩数据(长度为imgData.len - 图片头长度(16或20))。 d. 解压数据(如果cVer不为0)得到原始数据(长度应为width*height+_cgpLen)。 e. 如果_cgpLen>0,则从解压后的数据中取出最后_cgpLen字节作为图片自带调色板数据,并构建完整调色板(768字节,其中16-251用这708字节填充)。 f. 像素数据部分为解压后的数据的前width*height字节。 g. 创建一个width*height的ARGB数组,遍历每个像素,根据像素值在完整调色板中取颜色(BGR),并转换为ARGB(如果BGR全0,则透明?资料中代码是:如果调色板颜色不全0,则设置alpha为0xff,否则为0?但资料代码是:如果非0,则设置0xff000000,否则为0?但实际代码是:如果调色板颜色三个分量不全0,则设置alpha为0xff,否则不设置(但初始为0))。 h. 注意:图片数据是竖向反向的,所以第一行像素在数组的最后一行。因此,我们在填充位图时,从最后一行开始填充(或者将像素数据按行反转)。 i. 使用GDI+创建位图并保存。 解压函数decodeImgData的实现(假设为RLE): - 输入:压缩数据指针和长度。 - 输出:解压后的数据(需要提前分配足够空间,根据头中的width*height+_cgpLen,或者根据头中的len?但解压后的长度应该是width*height+_cgpLen)。 - 算法: unsigned char* decodeImgData(unsigned char* encoded, int encodedLen, int expectedLen) { unsigned char* decoded = new unsigned char[expectedLen]; // 根据预期长度分配 int index = 0; // 解码后数据写入位置 int pos = 0; // 压缩数据读取位置 while (pos < encodedLen && index < expectedLen) { unsigned char flag = encoded[pos++]; if (flag & 0x80) { // 重复:低7位表示重复次数(实际次数= (flag & 0x7f) + 1) int count = (flag & 0x7f) + 1; if (pos >= encodedLen) break; unsigned char value = encoded[pos++]; for (int i = 0; i < count; i++) { if (index >= expectedLen) break; decoded[index++] = value; } } else { // 不重复:低7位表示后面连续的不重复字节数(实际数量= flag + 1) int count = flag + 1; if (pos + count > encodedLen) break; for (int i = 0; i < count; i++) { if (index >= expectedLen) break; decoded[index++] = encoded[pos++]; } } } // 如果解码数据不足expectedLen,剩余部分补0(或者报错?) while (index < expectedLen) { decoded[index++] = 0; } return decoded; } 注意:expectedLen = width * height + _cgpLen 由于资料中提到的解压后长度应该等于expectedLen,所以我们可以用这个长度来分配。 代码实现: 由于时间关系,我们只实现核心功能。注意错误处理。 我们将编写一个类CGraphicExtractor,包含以下方法: - bool LoadPalette(const std::string& filename); // 加载外部调色板文件,构建完整调色板(768字节) - bool ReadIndexFile(const std::string& filename); // 读取索引文件 - bool ExtractAllImages(const std::string& graphicBinFile, const std::string& outputDir); // 提取所有图片到输出目录 注意:图库文件可能有多个,索引文件也有多个。但这里我们假设一次处理一个图库和一个索引。 由于原资料中提到了多个图库和索引,所以我们可以分别处理。 开始编码: 注意:使用#pragma pack(1)确保结构体对齐正确。</think>根据您提供的魔力宝贝图库文件格式,我将实现一个完整的图片提取工具。以下是C++解决方案: ```cpp #include <iostream> #include <fstream> #include <vector> #include <map> #include <windows.h> #include <gdiplus.h> #pragma comment(lib, "gdiplus.lib") // 使用1字节对齐确保结构体大小正确 #pragma pack(push, 1) struct imgInfoHead { unsigned int id; unsigned int addr; unsigned int len; long xOffset; long yOffset; unsigned int width; unsigned int height; unsigned char tileEast; unsigned char tileSouth; unsigned char flag; unsigned char unKnow[5]; long tileId; }; struct imgData { unsigned char cName[2]; unsigned char cVer; unsigned char cUnknow; unsigned int width; unsigned int height; unsigned int len; }; #pragma pack(pop) class GraphicExtractor { private: std::vector<imgInfoHead> imageInfos; std::map<std::string, std::vector<BYTE>> palettes; ULONG_PTR gdiplusToken; // 初始化GDI+ void InitGDIplus() { Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr); } // 加载调色板文件 bool LoadPalette(const std::string& path) { std::ifstream file(path, std::ios::binary); if (!file) return false; file.seekg(0, std::ios::end); size_t size = file.tellg(); file.seekg(0, std::ios::beg); if (size != 708) { std::cerr << "Invalid palette size: " << size << std::endl; return false; } std::vector<BYTE> paletteData(size); file.read(reinterpret_cast<char*>(paletteData.data()), size); palettes[path] = paletteData; return true; } // RLE解压算法 std::vector<BYTE> DecompressRLE(const BYTE* input, size_t inputSize, size_t expectedSize) { std::vector<BYTE> output; output.reserve(expectedSize); size_t pos = 0; while (pos < inputSize && output.size() < expectedSize) { BYTE flag = input[pos++]; if (flag & 0x80) { // 重复模式 int count = (flag & 0x7F) + 1; if (pos >= inputSize) break; BYTE value = input[pos++]; for (int i = 0; i < count; i++) { output.push_back(value); } } else { // 非重复模式 int count = flag + 1; if (pos + count > inputSize) break; for (int i = 0; i < count; i++) { output.push_back(input[pos++]); } } } // 填充不足部分 while (output.size() < expectedSize) { output.push_back(0); } return output; } // 获取GDI+编码器CLSID int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT num = 0; UINT size = 0; Gdiplus::GetImageEncodersSize(&num, &size); if (size == 0) return -1; auto pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size)); if (!pImageCodecInfo) return -1; Gdiplus::GetImageEncoders(num, size, pImageCodecInfo); for (UINT i = 0; i < num; i++) { if (wcscmp(pImageCodecInfo[i].MimeType, format) == 0) { *pClsid = pImageCodecInfo[i].Clsid; free(pImageCodecInfo); return i; } } free(pImageCodecInfo); return -1; } public: GraphicExtractor() { InitGDIplus(); // 加载默认调色板 LoadPalette("bin\\pal\\palet_08.cgp"); } ~GraphicExtractor() { Gdiplus::GdiplusShutdown(gdiplusToken); } // 读取索引文件 bool ReadIndexFile(const std::string& indexPath) { std::ifstream file(indexPath, std::ios::binary); if (!file) { std::cerr << "Failed to open index file: " << indexPath << std::endl; return false; } while (!file.eof()) { imgInfoHead head; file.read(reinterpret_cast<char*>(&head), sizeof(imgInfoHead)); if (file.gcount() != sizeof(imgInfoHead)) break; imageInfos.push_back(head); } std::cout << "Loaded " << imageInfos.size() << " image entries" << std::endl; return true; } // 提取图片 void ExtractImages(const std::string& binPath, const std::string& outputDir) { std::ifstream binFile(binPath, std::ios::binary); if (!binFile) { std::cerr << "Failed to open bin file: " << binPath << std::endl; return; } CreateDirectoryA(outputDir.c_str(), nullptr); for (const auto& info : imageInfos) { binFile.seekg(info.addr); imgData header; binFile.read(reinterpret_cast<char*>(&header), sizeof(imgData)); if (header.width > 5000 || header.height > 5000) { std::cerr << "Invalid image dimensions: " << header.width << "x" << header.height << std::endl; continue; } size_t dataSize = header.len - sizeof(imgData); unsigned int cgpLen = 0; // 处理扩展调色板 if (header.cVer == 3) { binFile.read(reinterpret_cast<char*>(&cgpLen), 4); dataSize -= 4; } // 读取图像数据 std::vector<BYTE> compressedData(dataSize); binFile.read(reinterpret_cast<char*>(compressedData.data()), dataSize); // 解压数据 size_t expectedSize = header.width * header.height + cgpLen; std::vector<BYTE> decompressed; if (header.cVer == 0) { decompressed = compressedData; } else if (header.cVer == 1 || header.cVer == 3) { decompressed = DecompressRLE(compressedData.data(), dataSize, expectedSize); } else { std::cerr << "Unsupported compression type: " << (int)header.cVer << std::endl; continue; } // 准备调色板 const BYTE* palette = palettes["bin\\pal\\palet_08.cgp"].data(); if (cgpLen > 0 && decompressed.size() >= expectedSize) { palette = decompressed.data() + header.width * header.height; } // 创建位图 Gdiplus::Bitmap bitmap(header.width, header.height, PixelFormat32bppARGB); // 填充像素数据(竖向翻转) for (unsigned int y = 0; y < header.height; y++) { for (unsigned int x = 0; x < header.width; x++) { unsigned int invY = header.height - y - 1; BYTE colorIdx = decompressed[invY * header.width + x]; // 获取调色板颜色 (BGR格式) const BYTE* color = palette + colorIdx * 3; BYTE b = color[0]; BYTE g = color[1]; BYTE r = color[2]; // 设置像素(透明处理) if (r == 0 && g == 0 && b == 0) { bitmap.SetPixel(x, y, Gdiplus::Color(0, 0, 0, 0)); } else { bitmap.SetPixel(x, y, Gdiplus::Color(255, r, g, b)); } } } // 保存图片 std::wstring filename = std::to_wstring(info.id) + L".png"; std::wstring fullPath = std::wstring(outputDir.begin(), outputDir.end()) + L"\\" + filename; CLSID pngClsid; if (GetEncoderClsid(L"image/png", &pngClsid) != -1) { bitmap.Save(fullPath.c_str(), &pngClsid, nullptr); std::wcout << L"Saved: " << fullPath << std::endl; } else { std::cerr << "Failed to get PNG encoder" << std::endl; } } } }; int main() { GraphicExtractor extractor; // 示例使用路径 - 实际使用时需要修改 extractor.ReadIndexFile("bin\\GraphicInfo_20"); extractor.ExtractImages("bin\\Graphic_20", "extracted_images"); return 0; } ``` ### 代码解释: 1. **结构体定义**: - `imgInfoHead`:40字节的索引文件结构 - `imgData`:16字节的图像文件头结构(当cVer=3时为20字节) 2. **核心类功能**: - `LoadPalette`:加载708字节的CGP调色板文件 - `DecompressRLE`:实现RLE解压算法(处理cVer=1和3的压缩数据) - `ReadIndexFile`:读取索引文件并解析所有图片信息 - `ExtractImages`:提取并保存图片 3. **图像处理流程**: - 定位图库文件中的图片数据 - 处理扩展调色板(cVer=3时) - RLE解压图像数据 - 使用调色板将索引颜色转换为RGB - 竖向翻转图像(从下到上存储) - 保存为PNG格式(透明处理全黑像素) 4. **特殊处理**: - 调色板索引0对应游戏调色板16号颜色 - cVer=0:未压缩数据直接使用 - cVer=1/3:使用RLE解压 - 自动处理图片自带调色板(优先使用) ### 使用说明: 1. 编译时需要链接GDI+库 2. 准备游戏bin目录和pal目录 3. 修改main函数中的文件路径 4. 运行程序提取图片到指定目录 ### 注意事项: - 需要处理游戏文件的路径问题 - 默认使用palet_08.cgp调色板 - 输出PNG格式支持透明度 - 大尺寸图片(>5000像素)会被跳过
阅读全文

最新推荐

recommend-type

前端开发基于jQuery的选择器与DOM操作技术:网页元素精准定位及动态交互功能实现

内容概要:本文系统介绍了jQuery的基础知识,涵盖其概念、优势、开发环境搭建、核心语法与选择器、DOM遍历与操作方法,以及事件处理机制。文章强调jQuery作为轻量级JavaScript库在简化DOM操作、跨浏览器兼容性及提升开发效率方面的突出作用,并通过大量代码示例详细讲解了选择器(如标签、类、ID、属性、自定义及表单选择器)、DOM遍历方法(如filter、next、siblings等)、元素访问方式(.get()和索引访问)以及事件绑定与委托(如on、off、hover、ready等),帮助读者掌握jQuery的核心使用技巧。; 适合人群:具备HTML、CSS和JavaScript基础,初入前端领域的开发者或希望巩固jQuery基础的1-3年经验研发人员。; 使用场景及目标:①快速实现DOM元素选取与操作,提升页面交互开发效率;②理解jQuery事件机制与DOM遍历逻辑,用于传统项目维护或兼容性开发;③为学习现代前端框架前打下扎实的JavaScript操作基础。; 阅读建议:建议结合文中示例动手实践,重点理解选择器的使用场景与事件委托机制,注意区分jQuery对象与原生DOM对象的操作差异,并在实际项目中逐步应用所学内容以加深理解。
recommend-type

DeepSeek类AI在图书馆智慧服务中的应用与技术考量.docx

DeepSeek类AI在图书馆智慧服务中的应用与技术考量.docx
recommend-type

jsfuck自动解码工具.zip

jsfuck自动解码工具
recommend-type

COMSOL仿真模型展现手性结构

利用COMSOL软件进行手性结构电磁仿真的具体步骤和技术细节。主要内容涵盖如何构建手性结构的通用模型,以及如何精确计算不同极化状态(如左旋圆极化、右旋圆极化)下的透射率(Tlr, Trl, Trr, Tll)和反射率(Rlr, Rrl, Rrr, Rll)分量。文中特别强调了极化分量计算的关键点,包括正确应用MATLAB脚本进行电场分量的矢量合成与投影运算,确保极化方向的一致性和准确性。此外,还提供了能量守恒检查的方法,用于验证模型的可靠性。 适合人群:从事电磁仿真、光学器件设计的研究人员和工程师,尤其是那些需要深入了解手性材料电磁特性的专业人士。 使用场景及目标:适用于希望掌握COMSOL多物理场仿真工具在手性结构电磁特性研究中的应用,旨在提高仿真精度和效率,避免常见错误,确保仿真结果的可靠性和一致性。 其他说明:文中提供的MATLAB代码片段有助于读者更好地理解和实施具体的计算过程,同时提醒了一些常见的陷阱和注意事项,帮助用户规避潜在的问题。
recommend-type

VMD-MFRFNN_VMD-MFRFNN.zip

VMD-MFRFNN_VMD-MFRFNN.zip
recommend-type

Info2007v1.0更新至v2.0:优化管理与前台功能

根据提供的文件信息,可以挖掘出以下知识点: ### 标题知识点: 1. **免费时代WEB程序INFO2007 V1.0:** - 该标题表明存在一个名为INFO2007的WEB程序版本1.0,该版本是在免费时代推出的,可能意味着该程序是开源的或者提供免费下载。 ### 描述知识点: 1. **软件缺陷说明:** - 开发者提到程序存在BUG(程序缺陷),并提供了一个更新和反馈的渠道,说明软件仍在开发中,且有后续版本计划。 2. **联系方式:** - 开发者提供了QQ和邮箱作为联系方式,用于反馈问题或询问更新情况。 3. **Info2007v2.0更新内容:** - 提及了升级后的版本INFO2007v2.0新增功能,包括数据库结构变化(添加会员和公告表)、后台管理功能的增加与优化、前台功能的增加与优化等。 4. **安装要求:** - 软件需要特定的服务器环境支持,比如FSO(文件系统对象)、数据采集功能和JMAIL(邮件发送组件)。 5. **配置与安装细节:** - 对config.asp下的目录配置和pageurlsa变量做了说明,这些通常涉及程序的运行环境和安全设置。 6. **默认登录信息:** - 提供了默认的管理员用户名和密码,以及后台管理的默认目录,这对于安装和测试程序很重要。 7. **使用前的必要步骤:** - 强调了解压后生成静态页面的重要性,这可能是确保网站内容可被正确浏览的前置操作。 ### 标签知识点: 1. **ASP源码其他类别:** - 这表明该程序使用ASP(Active Server Pages)作为后端编程语言,并且归类于其他类别,可能意味着它不局限于某一特定功能或领域。 ### 压缩包文件名称列表知识点: 1. **www.codejia.com:** - 这个文件名可能指示了程序被托管或下载的来源网站,也暗示了可能含有与网站域名相关的程序文件。 ### 综合知识点: 1. **软件开发与维护:** - 从描述中可以看出开发者在推动软件的持续改进,并鼓励用户参与软件的测试和反馈过程。 2. **软件环境配置:** - 软件对运行环境有所要求,特别是服务器端的支持,需要了解FSO、数据采集、JMAIL等组件的使用和配置。 3. **后台管理系统:** - 更新内容中提及的后台管理功能,如会员管理、公告管理、文章管理等,显示了该程序提供了一套用于网站内容和用户管理的后台解决方案。 4. **前台展示优化:** - 对前台页面的优化和增加功能,如会员注册、文章页、下载页和分类栏目的改进,说明了对用户体验的重视。 5. **安全与权限控制:** - 默认用户名和密码的提供,以及后台目录的默认设置,强调了安装过程中应立即更改编译以提高安全性。 6. **静态页面生成:** - 生成静态页面作为必要步骤可能涉及到网站的性能优化和安全措施。 7. **开源与社区支持:** - 由于提及了更新的可能和用户反馈渠道,这表明软件具有一定的开源特性或至少鼓励社区参与。 综上所述,这些知识点涵盖了软件开发的常见方面,包括软件生命周期的维护、功能更新、环境配置、安全实践以及优化用户体验。了解和掌握这些知识点可以帮助开发者和用户更好地利用和改进免费时代WEB程序INFO2007 V1.0。
recommend-type

Rust测试实战:错误处理、环境变量与模拟服务器

### Rust 测试实战:错误处理、环境变量与模拟服务器 在 Rust 开发中,测试是确保代码质量和稳定性的重要环节。本文将深入探讨 Rust 中的测试技巧,包括错误处理、使用环境变量测试 Config 模块以及使用模拟服务器测试 profanity 模块。 #### 1. 错误处理与比较 在 Rust 中,我们可以为自定义错误类型实现 `std::fmt::Display` 特征,以便将错误转换为字符串。以下是一个示例: ```rust impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::For
recommend-type

请分析下面代码:<tbody> <#if (paginationSupport.items)?has_content> <#list paginationSupport.items?sort_by('caseNo') as s> <tr class="b"> <td><a href="../user/viewRequestForm.action?requestFormId=${s.id}">${s.caseNo?default("Not Assigned")?if_exists}</a></td> <td>${s.lotId?if_exists}</td> <td><@m.directoryLink s.applicant?if_exists /></td> <td>${s.prodId?if_exists}</td> <td>${s.lotStageId?if_exists}</td> <td>${s.status?if_exists}</td> <td> <#if s.status!="Waiting For STR"> <#if s.workflowId?exists> <#list s.queuedOwners as owner> <#if owner?matches("[MmAaEeTt]\\d{3}.*")> <@m.directoryLink owner/> <#elseif owner?matches("[K-Z][A-Z][A-Z].*[_].*")> <#list adminService.loadGroupMembersByName(owner) as member> <@m.directoryLink member/><!--by Group--> </#list> <#else> <@m.orgManager owner?if_exists/><!--by Org--> </#if> </#list> </#if> <#else> <#if s.ercCategory=="Input the STR Number"||s.ercCategory=="STR"||s.ercCategory=="MSTR"> STR NO:<a href="${ercService.strLink?if_exists}${s.strEnNo?if_exists}" target="_blank">${s.strEnNo?if_exists}</a> is not effective <#else> EN NO:<a href="${ercService.enLink?if_exists}${s.strEnNo?if_exists}" target="_blank">${s.strEnNo?if_exists}</a> is not effective </#if> </#if> </td> <td><#if s.submitDate?exists>${s.submitDate?datetime?if_exists}</#if></td> <td>${s.purpose?default('')}</td> </tr> </#list> </#if> </tbody>

这段代码是使用 FreeMarker 模板引擎生成 HTML 表格内容的一部分,主要功能是根据分页数据展示请求表单的信息。以下是代码的详细分析: --- ### 1. **条件判断与循环** ```html <#if (paginationSupport.items)?has_content> ``` 判断 `paginationSupport.items` 是否存在且非空,只有存在数据时才执行表格渲染。 ```html <#list paginationSupport.items?sort_by('caseNo') as s> ``` 对 `paginationSupport.item
recommend-type

op-drone:监控未平仓头寸的市场利器

标题中提到的“op-drone”很可能是指一个特定的监控系统或服务的名称,它被用来“持续监控市场中的未平仓头寸”。未平仓头寸是指在金融市场上尚未完成交易结算的买卖双方的持仓数量。监控未平仓头寸对于市场参与者来说至关重要,因为它可以提供有关市场流动性和投资者情绪的重要信息。 在深入探讨之前,需要了解几个基础概念: 1. 未平仓头寸:指交易者在期货、期权、外汇或其他衍生品市场上建立的但尚未平仓的头寸。这些头寸在到期前仍然具有价值,而且市场上的价格变动会对它们的总体价值产生影响。 2. 持续监控:这通常是指使用软件工具或服务不断跟踪和分析市场数据的过程。持续监控可帮助交易者或市场分析师及时捕捉市场的动态变化,并根据最新情况做出交易决策。 3. 市场监控系统:这类系统通常具备收集实时数据、分析市场趋势、识别异常交易行为等多种功能。它们对于投资者了解市场状况、进行风险管理以及制定交易策略至关重要。 从描述中可以推断出,op-drone是一个专门用于持续监控未平仓头寸的系统或服务。这种系统需要具备以下功能: 1. 数据收集:系统需要有能力实时收集金融市场中的数据,包括但不限于期货、期权、股票、债券等金融产品的交易信息。 2. 数据分析:通过算法或机器学习技术分析收集到的数据,识别市场趋势、投资者行为模式以及潜在风险。 3. 异常检测:能够识别出市场中的异常交易活动,比如未平仓头寸的急剧变化,这可能是市场重大变动的前兆。 4. 风险预警:系统应能向用户发出风险预警,告知用户潜在的市场风险,帮助他们进行风险管理。 5. 报告与可视化:提供详细的数据报告和可视化图表,帮助用户更直观地理解市场状况和未平仓头寸变化。 此外,虽然文件中未提供标签和具体的文件名称列表,但可以推测“op-drone-main”可能是系统中的一个核心组件或主程序的名称。这个组件可能是整个op-drone系统运行的基础,负责处理大部分的监控和分析功能。 综合以上信息,我们可以得出一个结论:op-drone是一个专门设计用于监控金融市场上未平仓头寸的系统或服务。它可能具备实时数据收集和分析、异常行为识别、风险预警以及报告和可视化展示等多种功能,从而帮助用户在复杂的市场环境中做出更加明智的决策。对于需要精确把握市场动态、进行风险管理的金融分析师、交易员、投资组合经理等专业人士来说,这样的系统是不可或缺的工具。
recommend-type

RustWeb服务部署与环境变量配置全解析

### Rust Web 服务部署与环境变量配置全解析 在 Rust 开发中,完成业务逻辑编写后,将应用部署到生产环境是关键的下一步。本文将深入探讨 Rust 应用部署过程中的重要环节,包括环境变量的配置、二进制文件的优化以及跨平台编译等内容。 #### 1. 认证与授权相关未覆盖内容 在认证和授权方面,有一些内容未详细涉及。例如,设置会话数据库来存储令牌,以便在数据泄露或用户有其他需求时使令牌失效。可以通过设置 Redis 实例,在生成每个令牌时,不仅将结果返回给客户端,还将其存储在 Redis 键值存储中。 另外,重置用户密码和创建刷新令牌也是重要的话题。重置用户密码可以通过创建一个