简介:面向易语言初中级开发者及游戏素材处理爱好者,这套图片提取源码演示了如何借助易语言常用支持库,通过动画框组件将石器时代客户端中存储的图片数据解析出来并动态展示,适用于位图数据读取、动画框渲染、游戏资源拆包等典型学习场景。资源包共4个文件,包含1个.e源程序、2个整理自九月星空社区的魔力宝贝Bin文件格式与图像压缩格式解析文档,以及1个txt使用前说明,整体仅33KB,体量虽小但结构完整。目前已有344人学习浏览,适合作为图形图像类源码入门和练手的参考案例。通过阅读源码可掌握调用支持库读取外部图片数据、在动画框中实时刷新的具体写法;配套的htm文档补充了游戏图像压缩格式的背景知识,有助于理解图片提取的底层原理,并能迁移到其他复古游戏素材查看与提取工具的开发中。txt说明对运行库配置和动画框使用中的常见问题给出了提示,排错路径明确,对新手相当友好。
1. 易语言提取石器时代图片:先解析容器,再谈导出
很多人第一次拿到石器时代客户端里的资源文件,双击打开全是乱码,以为加密了。实际上那只是打包容器,图片数据完整地躺在里面,缺的是索引解析这一步。用易语言做这件事,核心路径是:读取二进制 → 定位条目表 → 按条目取出数据 → 还原成 BMP 或 PNG。整个过程不依赖游戏本体运行,只要客户端资源文件还在,随时能提取。适合想把老端素材整理成素材库的开发者、做非商业复刻的爱好者,以及刚接触易语言字节集操作、想找一个完整练手项目的新手。这里最容易踩的坑不是代码写不出来,而是把容器格式和图片格式混在一起处理,先分清楚再说后续。
2. 石器时代资源文件的两种容器:读懂文件头与条目表再动手
2.1 图库文件与PAK包的差异:从魔数判断资源形态
石器时代的客户端资源在不同版本里差别很大,但大体上可以分成两类:一类是早期直接堆放位图数据的图库文件,另一类是后来统一打包的 PAK 型容器。判断方法非常简单:用十六进制编辑器打开文件,看开头两个字节是不是BM。如果是,说明这份文件本身就是一张 BMP 或者一组连续拼接的 BMP 数据,提取逻辑退化成“按位图信息头分割即可”。如果不是,那就要走容器解析流程。
常见 PAK 型文件的开头是一个固定魔数或者版本号,紧接着是索引区。索引区里每一条记录指向某个资源在文件中的绝对偏移,以及这个资源的原始长度。有的版本索引区还会包含文件名、压缩标记、时间戳等字段。我一般会先做一个快速扫描:从文件偏移 0 开始连续读 4 字节,如果这个数值小于文件总长度,并且从该位置读出的数据往上回溯能找到合理的位图头,那基本就能断定这是索引区的起始位置。
# 用命令行工具快速查看文件头,判断容器类型 xxd -l 64 client.gra | head -20这段命令只读前 64 字节,不会因为文件过大把内存撑满。输出结果里如果前两字节是424d(十六进制),说明是 BMP;如果是一串可读字符或者固定数值,则进入容器解析。注意有些端在 PAK 文件前部还有一个“分包头”,把实际资源数据又往后推了一段,解析偏移时要额外加上这个头长度。
2.2 条目表解析:偏移、长度、文件名是提取的三块拼图
容器解析的核心是建立“哪一段数据属于哪一张图片”的映射。最可靠的方式是从索引区逐条读取记录,而不是在文件里盲目搜索BM标记,因为图片数据内部也可能出现BM字节序列。典型索引记录布局如下表,不同端会有字段顺序和长度差异,但原理一致。
| 字段 | 起始偏移 | 长度 | 说明 |
|---|---|---|---|
| 数据偏移 | 0 | 4 | 图片数据离文件头的绝对字节偏移 |
| 数据长度 | 4 | 4 | 该条目的原始字节长度 |
| 文件名 | 8 | 32 | 定长字符串,不足补 0 |
| 压缩标记 | 40 | 1 | 0 不压缩,1 RLE,2 zlib |
拿到这个表之后,我用易语言写循环就心里有底了:每次从索引区读 41 字节,取前 8 字节作为偏移和长度,再取 32 字节作为文件名,然后跳到下一条记录。关键点是判断索引区结束位置。有的端在文件头里写明条目总数,有的端没有,只能靠“索引记录起始偏移不断累加,直到越过文件长度”来判断。稳妥做法是同时校验条目内数据偏移是否落在文件有效范围内,避免读取越界导致易语言崩溃。
2.3 压缩标记与数据边界:不压、RLE、zlib三种情况的判别
老端资源不压缩的居多,但后续版本为了缩小客户端体积,引入了压缩标记。读取条目头时先看压缩标记字段,再决定后续处理。这里最容易犯的错是:把压缩标记当成保留字段忽略,结果提取出来的图片全部花屏。
我的处理惯例是三步。第一步,根据压缩标记分派;第二步,如果标记是 zlib,用易语言调用外部 zlib 动态库的uncompress接口;第三步,解压完成后再次检查解压结果的前两字节是否为BM,不是则说明解压策略有误。RLE 压缩由于不同版本实现差异大,我通常先尝试按行解码,解码失败就退回按原始数据直接导出,至少保留数据不丢。
对于不压缩的条目,直接按“数据偏移+数据长度”取出字节集即可。压缩与否不影响最终图片格式判断,因为解压后的数据仍然是一份标准的 BMP 或带调色板的位图数据。真正的难点在于如何区分“压缩后数据”和“非压缩位图数据”,通过压缩标记字段是最稳定的方式,不要试图通过数据特征猜测。
3. 用易语言写提取器:读文件、遍历索引、还原位图的核心代码
3.1 环境准备:支持库与外部DLL的选择
易语言默认核心库就够处理大部分二进制解析,不需要额外安装复杂支持库。但如果你要解压 zlib 数据,就得准备一个 zlib 动态库,并在易语言里通过“DLL命令”声明外部接口。常见做法是把动态库放在程序运行目录下,写声明时注意参数类型:uncompress的入参是字节集指针和输出缓冲区指针,长度参数要按“整数型”传递。
我不建议在这个阶段用易语言的“压缩支持库”代替 zlib,因为条目数据是直接从一个字节偏移处截断的,压缩库函数往往会把整段数据当作压缩流处理,对边界不友好。自己声明 DLL 命令反而可控:
.版本 2 .程序集 窗口程序集_启动窗口 .DLL命令 zlib_解压, 整数型, "zlib.dll", "uncompress" .参数 目标缓冲区, 字节集, 传址 .参数 目标长度, 整数型, 传址 .参数 源数据, 字节集, 传址 .参数 源长度, 整数型这个声明里,传址关键字很关键。不传址的话,易语言可能只传递字节集指针本身,导致解压失败或者写坏内存。目标缓冲区用“取空白字节集(预计解压大小)”提前准备好,目标长度变量里存放缓冲区大小,解压成功后会被改写为实际解压长度。
3.2 读文件与目录遍历:字节集操作的中文命令写法
先把整个资源文件读入内存,操作上最直接。易语言里读入字节集命令可以一次读取,适合大小在几十 MB 以内的文件。读取完成后,用取字节集中间按偏移切数据,用取字节集数据把字节集转换成整数或短整数。下面这段是读取文件并遍历索引区的基础骨架:
.版本 2 .支持库 spec .子程序 读取资源并解析索引, 整数型 .参数 文件路径, 文本型 .参数 条目信息, 字节集, 参考 数组 .局部变量 文件号, 整数型 .局部变量 文件长度, 整数型 .局部变量 文件内容, 字节集 .局部变量 当前偏移, 整数型 .局部变量 数据偏移, 整数型 .局部变量 数据长度, 整数型 .局部变量 条目名称, 文本型 .局部变量 单条目内容, 字节集 文件号 = 打开文件 (文件路径, #读入, #无限制) 如果真 (文件号 = 0) 信息框 (“文件打开失败”, 0, , ) 返回 (0) 如果真结束 文件长度 = 取文件长度 (文件号) 文件内容 = 读入字节集 (文件号, 文件长度) 关闭文件 (文件号) 当前偏移 = 16 ' 这个值根据实际文件头大小调整 判断循环首 (当前偏移 + 44 ≤ 文件长度) 数据偏移 = 取字节集数据 (文件内容, 当前偏移, #整数型) 数据长度 = 取字节集数据 (文件内容, 当前偏移 + 4, #整数型) 条目名称 = 到文本 (取字节集中间 (文件内容, 当前偏移 + 8, 32)) 单条目内容 = 取字节集中间 (文件内容, 数据偏移 + 1, 数据长度) 加入成员 (条目信息, 单条目内容) 当前偏移 = 当前偏移 + 44 判断循环尾 () 返回 (取数组元素数目 (条目信息))这里两个参数要特别说明。第一,当前偏移初始值设为 16 是因为前面可能有文件头,但换一个端可能就不是 16,正确姿势是先扫描魔数位置再决定。第二,循环体内每条索引记录按 44 字节步进,这是基于“8 字节定位信息+32 字节文件名+4 字节扩展”的假设,实际端如果索引记录是 36 字节,这个步长就要改。写完这个循环后,先用调试输出打印条目数,和用十六进制编辑器看到的索引区记录数对比,对不上就调整步长。
3.3 位图还原:手动拼BMP文件头与调色板
从资源里取出来的数据不一定带完整 BMP 文件头,很多老端只存储位图信息头+调色板+像素数据。易语言里要把这些数据变成可预览、可另存的图片,就得手动把 14 字节的BITMAPFILEHEADER补回去。这个工作看起来繁琐,其实规律很强。
.子程序 裸数据转BMP文件, 字节集 .参数 图片源数据, 字节集 .参数 保存路径, 文本型 .局部变量 文件头, 字节集 .局部变量 数据头, 字节集 .局部变量 像素区, 字节集 .局部变量 信息头, 字节集 .局部变量 图像宽度, 整数型 .局部变量 图像高度, 整数型 .局部变量 颜色深度, 短整数型 如果真 (取字节集数据 (图片源数据, 1, #字节型) = 66 且 取字节集数据 (图片源数据, 2, #字节型) = 77) 写到文件 (保存路径, 图片源数据) 返回 (图片源数据) 如果真结束 ' 从第 18 字节起取宽度,第 22 字节起取高度,第 28 字节起取颜色深度 图像宽度 = 取字节集数据 (图片源数据, 19, #整数型) 图像高度 = 取字节集数据 (图片源数据, 23, #整数型) 颜色深度 = 取字节集数据 (图片源数据, 29, #短整数型) ' 构造 BITMAPFILEHEADER 文件头 = { 66, 77 } ' 'B','M' 文件头 = 文件头 + 到字节集 (14 + 取字节集长度 (图片源数据)) 文件头 = 文件头 + 到短整数 (0) 文件头 = 文件头 + 到短整数 (0) 文件头 = 文件头 + 到整数 (14 + 40 + 选择 (颜色深度 ≤ 8, 颜色深度 * 4, 0)) 写到文件 (保存路径, 文件头 + 图片源数据) 返回 (文件头 + 图片源数据)逻辑说明:先判断源数据是否已经是完整 BMP,是就直接写出;不是则补文件头。到字节集命令会把整数按小端序转成 4 字节,正好符合 BMP 文件头的要求。参数里最需要注意的是文件头偏移字段,也就是第 10 字节起的 4 字节,它表示像素数据从文件头开始算的偏移量。这个值一旦算错,图片查看器就会把调色板当像素显示。8 位及以下图片要加上调色板长度,16 位及以上则不需要。
3.4 写出与验证:用画板确认提取结果
提取出一批图片后,不要直接信任文件大小,推荐用易语言的画板组件做可视化验证。画板可以载入字节集形式的图片数据,然后你用取图片宽度和取图片高度检查尺寸,再用画板.画图片把图显示出来。
.局部变量 图片字节集, 字节集 .局部变量 临时图片, 字节集 .图片框1.图片 = 转换成可显示图片 (图片字节集)这段代码里的关键命令是转换成可显示图片,它会把带有正确文件头的 BMP 字节集转成易语言图片格式。如果这一步报“数据格式错误”,多半是手动拼的文件头有问题,优先检查偏移字段。验证通过后,再批量写盘。
4. 三个必调参数与两种图片格式:亮度、透明通道与尺寸修正
4.1 调色板偏移与颜色深度:8位图和16位图的不同处理
石器时代资源里最常碰到的图片格式是 8 位 256 色和 16 位高彩。8 位图在信息头之后紧跟着 1024 字节调色板,每个颜色占 4 字节(BGR 加一个保留字节);16 位图没有调色板,像素直接按两字节交错排列。解析时先用信息头第 28 字节处的颜色深度字段分支处理。
对 8 位图,像素数据里的每个字节都是调色板索引,你还得把调色板一并提取出来,否则导出的 BMP 会变成黑白的。有的端调色板里存的是 BGR 顺序,有的存 RGB 顺序,导出后发现红色和蓝色互换,就把调色板前三字节做一次交换再写入。这个细节很玄学,和具体端有关,建议先用一张已知颜色的图做校正。
4.2 透明通道修正:PNG输出与RGB特殊色替换
老端图片的“透明”通常不是靠 Alpha 通道实现,而是把调色板里的某个索引位预留为透明色。常见做法是索引 0 默认透明,或者用调色板里某个特殊颜色值标记透明像素。直接导出 BMP 时这些像素会显示为纯黑或者品红,很难看。
我一般处理方式:提取后遍历像素索引,把透明索引对应的像素改成纯绿色或者统一的 RGB 值,再用易语言的图像转换命令保存成 PNG 时手动处理。如果你不想在易语言里做逐像素操作,可以直接把原始 BMP 导出去,后续用外部图像库批量去底。但要注意去底阈值的设置,调得太高会把半透明边缘也抠掉。
4.3 尺寸字段异常:用像素行宽补齐还原真实宽高
信息头里的宽度和高度不一定可信。有些端在打包时会把高度字段写为 0,或者把实际高度的一半存入字段,目的是让图片查看器无法直接预览。应对方式是先读取宽度,实际高度按“像素总字节数 ÷ 每行字节数”反推。每行字节数要按 4 字节对齐,公式是:
| 颜色深度 | 每像素字节数 | 每行字节数计算公式 |
|---|---|---|
| 8位 | 1 | ((宽度 × 1 + 3) / 4) × 4 |
| 16位 | 2 | ((宽度 × 2 + 3) / 4) × 4 |
| 32位 | 4 | 宽度 × 4 |
如果反推出来的高度和字段里的高度不一致,用反推值覆盖字段值再拼文件头。这一步能解决一部分“导出来图片是斜的”的坑,因为行宽不对会导致像素错位。
5. 提取器避坑记录:读资源文件最常见的五个坑
5.1 大文件读入内存溢出
现象:文件超过 200MB 时,易语言程序直接闪退,或者读入时内存占用飙升到几百 MB。
原因:读入字节集一次性把整个文件载入内存。老端客户端动辄几百 MB,全部载入后字节集副本操作又额外占一份内存。
解决:换成分段读取。每次只读取索引区定位的数据段:先打开文件,用移到文件位置跳到数据偏移处,再读入字节集 (文件号, 数据长度),处理完当前条目再移下一处。这样内存占用就只和单张图片大小相关。
5.2 条目名乱码导致写出失败
现象:导出的文件名是乱码,或者包含非法字符如/ \ : * ?,写盘时直接报错。
原因:资源内部文件名可能是 UTF-8 编码,而中文 Windows 下易语言默认按 GBK 解析文本。
解决:取出文件名后先用字节集形式判断。遇到高字节内容,尝试用“编码转换”命令把 UTF-8 转为 GBK;转换失败则用索引序号作为文件名。写入前再做一次非法字符过滤。
5.3 调色板错位导致整张图花屏
现象:导出后有图能打开,但颜色完全错乱,像雪花一样;或者图分成上下两半。
原因:调色板长度计算错误。8 位图调色板是 256×4 字节,但有些资源实际存储的调色板只有 256×3 字节,多读的 256 字节把首行像素吞了。
解决:打印调色板区域的最后几字节,确认是00 00 00 00结尾还是00 00 00。如果是 3 字节结尾,在拼接文件头时把像素区偏移减去 256 字节,或者把调色板长度参数改成 768。
5.4 PAK解压失败后静默返回空
现象:zlib 解压函数返回值为负数,易语言里看不到具体错误,图片导出来是空文件。
原因:DLL 命令声明中目标长度参数没有传址,或者源数据里含有压缩流以外的尾巴字节。
解决:先验证单条数据:把源数据复制出来,取源长度字段。如果解压返回值是 -5 这类缓冲区不足码,就把目标缓冲区加倍重试;如果返回 -3,说明数据截断,检查数据偏移是否多加了一个文件头长度。
5.5 校验魔数不严格导致解析错位
现象:索引解析出来条目数正常,但每张图打开都是碎片,好像错位了一个固定偏移。
原因:文件头前面有自定义格式头,或者索引记录里数据偏移是从索引区开始计算,而不是从文件头开始。
解决:建立“偏移校准”逻辑:从索引区读第一条数据偏移,再读取该位置前两字节。如果不是BM,就循环检查偏移 + 2、偏移 + 4 等位置,直到找到BM,然后计算差值并统一补偿。
6. 批量导出到PNG:一个可复用的封装与动画帧编号技巧
6.1 封装一个统一的导出入口
把前面的解析、还原、写盘逻辑封装成一个子程序,入参是资源路径和导出目录,出参是成功导出数量。这样处理多份资源文件时只需要循环调用,不需要改内部代码。这里给出函数签名和调用示范。
.子程序 导出资源内全部图片, 整数型 .参数 资源路径, 文本型 .参数 导出目录, 文本型 .局部变量 条目数组, 字节集, 数组 .局部变量 循环变量, 整数型 .局部变量 图像字节集, 字节集 .局部变量 输出路径, 文本型 清空数组 (条目数组) 读取资源并解析索引 (资源路径, 条目数组) 计次循环首 (取数组元素数目 (条目数组), 循环变量) 图像字节集 = 裸数据转BMP文件 (条目数组 [循环变量], 导出目录 + “\temp.bmp”) 输出路径 = 导出目录 + “\img_” + 到文本 (循环变量) + “.bmp” 写到文件 (输出路径, 图像字节集) 计次循环尾 () 返回 (取数组元素数目 (条目数组))6.2 动画帧编号与文件名映射技巧
石器时代的角色和宠物素材通常是一组连续帧,提取后按原始条目顺序导出,文件名顺序不一定是播放顺序。我的做法是:解析出条目名后,尽可能保留原名字段,再在名字后面追加 4 位序号,保证排序稳定。如果原始条目名本身带有动作编号和帧号,比如末尾 4 位是帧序号,那直接取末尾数字排序即可。
批量导出完成后,我会额外生成一个index.txt,把每张图的原始偏移、长度、文件名逐行记录。这个文件相当于后悔药,后续想做批量重命名、素材分类或还原数据库时,不用重新解析资源,直接读标记文件就能对应上。这是我做资源提取类工具时养成的习惯,多写一个小小的元数据文件,能省掉后面几天的人工整理时间。
遇到提取结果和游戏内显示不一致时,先不要怀疑代码逻辑。重新用十六进制编辑器对比源数据和导出的 BMP,逐字节检查文件头和调色板,定位问题多半在偏移字段。这套流程我前后调过三四个不同版本的资源文件,核心骨架没变过,变的只有文件头大小和索引步长。希望帮到你。
本文还有配套的精品资源,点击获取