ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

易语言石器时代图片提取:PNG魔数定位与文件头还原源码解析

易语言石器时代图片提取:PNG魔数定位与文件头还原源码解析

简介:这是一套易语言编写的石器时代游戏图片提取源码,面向易语言初学者、游戏资源分析爱好者及图形图像学习者。源码调用易语言常用支持库,借助动画框控件加载并显示从石器时代客户端数据中提取的图片,清晰呈现了底层图片数据解析、坐标还原与实时展示的完整链路。压缩包共4个文件,包含1个易语言工程文件(.e),2个HTML格式的参考文档(整理自“九月星空”,系统介绍魔力宝贝Bin文件格式与图像压缩格式,可作为理解石器时代同类数据结构的对照资料),另有1个txt使用前说明,整包仅33KB,结构精简便于快速浏览。目前已有344人学习下载,适合想研究国产网络游戏早期图像资源提取思路的人;对照源码与文档即可梳理数据块读取、调色板处理等关键步骤,稍作改动还能扩展到其他基于Bin格式的游戏图片提取,实用性和可操作性都比较强。

1. 易语言石器时代图片提取:一份图形图像源码,把“换头PNG”打回原形

很多老网游的客户端图集并不是真正加密,而是把图片文件头整体往后挪了一段,前面塞上自定义的标识字节。易语言写的石器时代图片提取工具,做的就是把这层冒充文件头的字节剥掉,让图片管理软件重新认出里面的真实数据。这套图形图像源码走的是“扫描文件流、按PNG魔数定位、重写文件头”的路线,对想学易语言字节集操作的开发者非常友好,也让手里还留着老客户端资源的素材党能批量还原出可编辑的PNG。适合做网游玩家的美术DIY,也适合需要批量处理同类图片资源的从业者。下面从格式原理、编译环境、提取逻辑和踩坑记录四个角度,把这份源码完整拆开。

2. 先看格式再写代码:石器时代图片为什么能被易语言直接“识破”

2.1 不是加密,而是文件头被置换

客户端里的素材文件通常不是PNG也不是BMP,而是扩展名五花八门的DAT或自定义格式。用十六进制查看器打开,能在文件开头看到一段固定标识,比如58 47 01 00这种,这说明文件被加了一层“壳”。真正的图片数据并没有丢失,只是起点往后平移了几十甚至几百字节。这种做法的好处是客户端加载时按自己的解码器读取,普通人用看图软件打不开;坏处是只要写一个小程序顺着字节流找PNG魔数,就能把整张图还原成正常文件。

常见做法是先读取文件头8字节,如果和89 50 4E 47不匹配,就启动全文件扫描。扫描步长按1字节逐个比对,代价是文件稍大时速度会慢,但胜在不会漏。我处理过的几个客户端资源包里,偏移量最短的只有16字节,最长的到过3KB,都是同一个套路:自定义标识加版本号加图片索引。所以写扫描逻辑时,固定偏移是最不可靠的方案,逐字节搜索才是通用解。

这种黑匣子式的存储方式其实在资源类游戏里很常见,核心目的就是不让普通用户轻易拿到可编辑素材。只要理解了文件头置换这个基本思路,提取工具的核心逻辑就成立了一大半:不需要知道图片是怎么被加密的,只需要知道真实图片数据从哪里开始。

2.2 为什么用易语言处理这个场景

很多人会问,Python写这种脚本不是更快么。确实,Python的struct和PIL处理PNG很方便,但易语言在这个场景里有很实际的优势:字节集类型直接把文件数据当成一块内存,不需要显式管理指针和缓冲区,命令名称贴近自然语言,改起来直观。对于图形图像方向的源码来说,易语言自带的读入文件、寻找字节集、取字节集右边三条命令已经覆盖了提取流程的百分之八十。

另一个现实原因是产出物是独立exe,不需要目标机器装Python环境。游戏素材提取经常要在别人的电脑上跑,一台没有Python的机器会让脚本方案直接翻车;易语言静态编译出来的单文件程序,拷过去双击就能用。老工具圈子里大量图片处理小工具都用易语言写,也是因为这个分发便利性,这份源码选择易语言并不是偶然,而是这类场景下的常规选择。

字节集操作本身也比文本处理更直接。比如要从一个文件数据里切出一段,C++要算指针偏移和长度,易语言直接“取字节集中间”就完了。寻找字节集的命令能返回第一次匹配的位置,正好用来定位PNG魔数;取字节集右边能按长度从尾部截取,正好用来保存剥离头部之后的真实图片数据。这几个命令组合起来,提取流程不用引入任何第三方库。

2.3 资源包里对应哪几个功能模块

这套资源按功能可以切成三块:文件枚举器、格式检测器、图片还原器。文件枚举器负责把一个目录下所有文件按扩展名过滤,生成待处理清单;格式检测器读取每个文件的前16字节,判断是否包含PNG魔数;图片还原器负责剥离头部并落盘。三个模块串起来就完成了一整条提取流程,模块之间通过文本清单和字节集传递数据,替换起来也很方便。

功能模块职责关键命令
文件枚举器扫描目录,生成处理清单打开文件、读入一行
格式检测器定位PNG魔数与偏移量寻找字节集
图片还原器剥离头部、写回PNG取字节集右边、写到文件

枚举器生成的清单是纯文本,每行一个源文件路径。检测器读入这个清单,逐个处理;还原器只管接收字节集并输出文件。接口划分清楚以后,想增加新的图片格式支持,只需要在检测器里增加新的魔数定义,不需要动前面和后面的模块。

下面是从格式检测器里抽出来的核心逻辑,直接放到易语言工程里就能用。

.版本 2 .支持库 spec .子程序 检测PNG魔数, 逻辑型 .参数 文件路径, 文本型 .局部变量 文件数据, 字节集 .局部变量 PNG头, 字节集 文件数据 = 读入文件 (文件路径) 如果真 (取字节集长度 (文件数据) < 8) 返回 (假) PNG头 = { 137, 80, 78, 71, 13, 10, 26, 10 } 如果真 (寻找字节集 (文件数据, PNG头, 1) > 0) 返回 (真) 返回 (假)

这段代码先整体读入文件,再判断长度,最后用8字节完整魔数做匹配。之所以不用前4字节,是为了防止自定义头部里恰好出现89 50 4E 47这种疑似片段,导致误判。文件路径参数支持绝对路径和相对路径;如果你要检测的是超大文件,建议改成流式分段读取,否则一次读入几百MB会占满内存。

魔数定位是整个提取流程的地基,定位逻辑写扎实了,后面所有步骤都顺。易语言的字节集命令在处理这类“找头、切头、留尾”的操作时特别顺手,这也是这份资源的核心价值所在。

3. 把源码跑通:编译环境、支持库与第一个提取结果

3.1 环境与支持库配置

易语言版本建议5.3以上,5.3之后的版本对静态编译和Unicode支持更完整。打开源码包后,先确认有没有引用外部支持库;老工程经常带几个模块文件,缺失时会编译报错“找不到指定命令”。处理方式是检查支持库配置,把源码同目录下带的支持库文件加进去,不要只拷贝exe而不带模块。

静态编译在菜单栏“编译”里选“静态编译”,输出的是单文件exe。注意静态编译依赖链接器和运行时库,如果提示缺少编译器组件,需要回到易语言安装目录检查静态编译支持是否勾选。新手容易卡在“编译没报错,生成成功却运行闪退”这一步,原因多半是用了调试版输出或缺少运行库。第一次拿到资源,先别急着跑批量,把单文件检测先跑通。

这块环境配置看上去不起眼,但实际动手时翻车的概率很高。尤其是从老机器上拷贝工程文件,支持库路径对不上的情况很常见。我的习惯是拿到资源后先看程序面板里引用了哪些支持库,截图记录,再对照自己的易语言版本逐项核对。

3.2 路径常量与目录结构

源码里一般会有几个顶部常量,比如源目录、输出目录、日志文件路径。拿到资源后先改这三个,不需要动逻辑代码。输出目录建议单独建一个released文件夹,避免和源文件混在一起。目录路径结尾不要漏反斜杠,易语言的文本处理对拼接路径很敏感,漏掉以后会出现半个路径的错误。

批量处理前建议先跑单文件模式。把单个素材文件路径填到测试参数里,确认输出图片能被看图软件正常打开,再切到整目录模式。这种“先单后批”的节奏能帮你分清是格式问题还是扫描逻辑问题。单文件跑通了,后面批量出问题可以放心怀疑是遍历或计数的逻辑;单文件都没跑通,问题基本在格式识别或头部剥离上。

目录常量建议用绝对路径,不要用相对路径。原因很简单,相对路径依赖程序启动时的当前目录,双击exe和从命令行启动得到的结果可能不一样。绝对路径虽然换机器要改,但每次运行的结果都可预期,不会出现“昨天能跑今天不行”这种玄学问题。

3.3 第一个批量提取脚本

下面是从资源里抽出来的批量入口,按一份filelist.txt清单逐行处理。清单可以由文件枚举器先生成,也可以手写,每行一个源路径。

.版本 2 .支持库 spec .子程序 批量提取目录图片, 整数型 .参数 目录路径, 文本型 .参数 输出目录, 文本型 .局部变量 文件路径, 文本型 .局部变量 文件句柄, 整数型 .局部变量 输出文件, 文本型 .局部变量 成功数, 整数型 文件句柄 = 打开文件 (目录路径 + “\filelist.txt”, #读, #禁止写) 如果真 (文件句柄 > 0) 判断循环首 (是否在文件尾 (文件句柄) = 假) 文件路径 = 读入一行 (文件句柄) 如果真 (文件路径 ≠ “” 且 取文本右边 (文件路径, 4) = “.dat”) 输出文件 = 输出目录 + “\” + 取文件名 (文件路径) + “.png” 如果真 (还原PNG (读入文件 (文件路径), 输出文件)) 成功数 = 成功数 + 1 如果真结束 如果真结束 判断循环尾 () 关闭文件 (文件句柄) 返回 (成功数)

逻辑是打开清单文件,逐行读取,过滤后缀,每读到一个源文件就调用还原子程序并计数。输出文件名沿用源文件名,只改扩展名,方便追溯。第一个参数是清单所在目录,第二个参数是输出目录;目录结尾带反斜杠,拼接时不会出错。读入一行拿到的文本默认不含换行符,如果源文件路径里带空格也照样能读。

运行后先看成功数和实际文件数对得上不对得上。差几个没关系,多半是文件本身损坏或魔数偏移异常;如果差很多,就要回检测器排查是不是漏了一种自定义头。批量提取的日志建议单独落盘,不要只输出调试文本,否则关了程序就看不到了。

4. 提取逻辑拆解:从文件扫描到PNG还原的四个关键步骤

4.1 第一步:定位真实起始偏移

偏移定位决定整张图能不能被正确还原。前面说过,自定义头长度不固定,需要靠PNG魔数反推起点。魔数是8字节固定序列,从文件头开始逐字节比较,找到第一个匹配就认为这里是真实图像开始。

比较时有个细节:不能只比较前4字节,必须比对完整8字节。我见过某些客户端在头部末尾写了PNG四个ASCII字符,前4字节能对上,第5字节就断了;只用前4字节会把整个文件头错切进去,生成一张打不开的废图。用完整魔数的代价是每个文件多几次比较,现代机器上几乎无感。

如果源文件里出现多个PNG魔数,定位逻辑取第一个还是不取?我的经验是取第一个,因为客户端写文件时一般不会在自定义头里构造完整魔数;如果确实出现多个,对比每个候选位置后面的IHDR块是否合法,取合法的那一个。IHDR块的“IHDR”四字节也可以作为二次验证条件。

4.2 第二步:剥掉自定义头部字节

找到偏移后,原始文件从偏移位置到结尾就是完整PNG数据。易语言里取字节集右边正好对应这个操作,参数是“从右边取多少字节”,计算式为文件总长度减去偏移加一。

代码示例如下。

.子程序 还原PNG, 逻辑型 .参数 源图数据, 字节集 .参数 输出路径, 文本型 .局部变量 PNG起始, 整数型 .局部变量 PNG头, 字节集 .局部变量 真实图片, 字节集 PNG头 = { 137, 80, 78, 71, 13, 10, 26, 10 } PNG起始 = 寻找字节集 (源图数据, PNG头, 1) 如果真 (PNG起始 < 1) 返回 (假) 真实图片 = 取字节集右边 (源图数据, 取字节集长度 (源图数据) - PNG起始 + 1) 写到文件 (输出路径, 真实图片) 返回 (真)

这里关键参数就是PNG起始,它决定截取边界。寻找字节集返回的是1基索引,真实图片长度等于总长度减起始加一,恰好从魔数第一个字节开始保留。写到文件失败时要检查输出路径的父目录是否存在,易语言没有自动创建目录的能力,目录不存在时写文件会直接失败。

参数含义取值建议
PNG起始 < 1未找到魔数跳过该文件,记录日志
PNG起始偏移大头部长度大无需处理,截取逻辑通用
输出路径父目录不存在写文件失败提前用“创建目录”命令建好

4.3 第三步:修正调色板与位深

一部分老式客户端图走的是调色板索引格式,头被剥掉后虽然能打开,但颜色全乱。现象是图像轮廓清楚、颜色像被冲洗过,问题出在调色板数据没有被一起还原。正确做法是在魔数定位后继续解析PNG内部的PLTE数据块,确认调色板长度是否等于256项乘以4字节,也就是1024字节。

偏移定位本身不影响调色板,调色板错乱多源自源文件被二次修改。如果你提取的图有部分颜色不对,去对比原始字节集里PLTE段和标准PNG结构的差异,常见问题是源文件把调色板放在文件尾部、顺序颠倒。验证方法很简单,把提取结果拖进十六进制编辑器,看PLTE块偏移在不在IDAT之前。

处理调色板问题时有一点要注意:易语言取字节集中间返回的是字节集,不能直接当整数用。转成整数前先判断取出的字节集长度是不是4,防止越界。我这边的习惯是把PLTE载入一个字节集数组,每个元素固定4字节,再按索引取颜色值,这样逻辑清楚也好调试。

4.4 第四步:写文件与异常跳过

落盘前建议做两件小事:一是检查文件尺寸是否大于8字节,小于8字节直接跳过;二是记录还原失败的文件路径到日志。批量处理时日志比弹窗有用,弹窗只能确认单文件,日志能让你扫一眼就知道哪些文件出了岔子。

写文件后把输出路径加入成功清单,这个清单下一步可以用来做CRC对比。另外输出文件命名不要用原始路径里的特殊字符,Windows下冒号和问号会直接让写文件失败,提前做一遍文本替换比报错后再改省事。输出文件名里最好保留一定的序号信息,这样后续对照资源表定位素材时能省不少时间。

异常跳过这个设计看似简单,实际上决定了批量工具在真实数据上的存活率。客户端资源包经常会混入损坏文件、0字节空文件和纯文本说明,检测逻辑不完善时这些杂项文件会导致整个批次中断。把每类异常单独记日志,提取完成后按日志分类处理,比一次弹一个错误框要高效得多。

5. 避坑手册:易语言图片工具的五个翻车现场与修复方案

5.1 编译出来的exe被杀毒软件误报

现象:静态编译后的提取工具第一次运行就被拦截,甚至直接被删除。

原因:易语言编译产物特征比较固定,杀毒软件对未签名的中文编程工具误报率偏高;再加上工具行为确实是读取大量文件并写文件,容易被启发式规则盯上。

解决:优先用静态编译,不勾选加壳选项,加壳反而更容易触发误报。发布前用不同的杀毒引擎扫一遍,若只是单个引擎报毒,多半是误报。把源码交给使用者自己编译是另一个实际可行的方案,源码就在包里,自己编译出来的文件通常不会被拦。从那以后我再也不把编译好的exe当唯一交付物,每次都提醒对方先编译看效果。

5.2 提取出来的图颜色全乱

现象:PNG能打开,但画面像底片,颜色整体错位。

原因:文件头剥离正确,但调色板段没有同步修正,通常是源文件用了自定义调色板表。

解决:扩展检测器,增加PLTE块校验。如果调色板长度不是1024字节(256色乘4字节),就按源文件的调色板偏移重新映射。这块没有通用解,因为不同客户端格式不一样,但排查思路是固定的:先用十六进制查看器确认IDAT段位置,再看PLTE是否存在以及长度对不对。颜色错乱比文件打不开更隐蔽,它不会报错,只有肉眼检查才能发现。

5.3 大图批量处理闪退

现象:批量处理到某张大图时,程序直接退出,之前处理好的结果也没写入磁盘。

原因:批量模式把整个文件读入内存,源文件过大或内存碎片过多时进程崩溃。

解决:把“读入文件”改成分段处理,先读头部和尾部,确认魔数位置后再截取需要的数据段。我一般对超过100MB的文件单独开一个分支,输出日志后继续处理下一个,不让单张坏图拖垮整个批次。如果工程里用了多线程并行提取,更要加内存限制,否则几个大文件同时读入很容易把32位进程的2GB地址空间打满。

5.4 中文路径找不到文件

现象:文件存在但程序提示找不到,路径里的中文全部变成乱码。

原因:源码没有按Unicode方式编译,文本编码默认是GBK,遇到UTF-8路径就错位。

解决:编译时在系统配置里打开Unicode支持,或者在打开文件前统一做编码转换。最省事的方式是让清单文件使用ANSI编码保存,和易语言的默认编码对齐,这样读入路径不会出乱码。这个问题在Windows 10以上系统特别明显,因为系统区域设置的默认编码已经不是GBK了。如果你拿到源码后在打开文件那一步报错,优先怀疑编码,这是易语言老工程最常见的坑。

5.5 处理到一半卡死

现象:批量处理在同一个文件上卡住,CPU占用不高但迟迟不进入下一个文件。

原因:源文件魔数定位逻辑进入死循环,或者文件被其他程序占用导致打开失败后频繁重试。

解决:给每个文件处理流程加超时保护,超过5秒就放弃。下面这段是加了超时判断的简化入口,核心是处理完后判断耗时,超时就把半成品删掉。

.子程序 单文件提取带超时保护, 逻辑型 .参数 源文件, 文本型 .参数 输出文件, 文本型 .局部变量 开始时间, 整数型 .局部变量 数据, 字节集 开始时间 = 取启动时间 () 数据 = 读入文件 (源文件) 如果真 (还原PNG (数据, 输出文件)) 返回 (真) 如果真 (取启动时间 () - 开始时间 > 5000) 删除文件 (输出文件) 返回 (假) 返回 (假)

这段代码把读入和还原放在一起计时,超过5秒按失败处理并清理半成品。正常PNG提取单张都在毫秒级,5秒足够区分“大文件处理慢”和“死循环”。输出日志时记录超时的文件名,处理完统一复查那些文件是不是被占用或者格式异常。

6. 进阶验证:用十六进制和宽高对照确认提取成果,再反向封包

6.1 十六进制检查与IHDR宽高解析

提取结果可用性不能只看能不能打开。正规做法是解析PNG的IHDR数据块,宽度字段在文件头之后第16字节、第17字节处,占4字节,用易语言取字节集中间就能读到。我拿到还原图后会做两步验证:第一看文件前8字节是否正好是PNG魔数;第二把IHDR里读出的宽高和客户端资源表里的尺寸对一下,对得上才算真正通过。

.局部变量 尺寸块, 字节集 .局部变量 图片宽度, 整数型 尺寸块 = 取字节集中间 (输出文件数据, 17, 4) 图片宽度 = 到整数 (尺寸块) 输出调试文本 (“图片宽度: ” + 到文本 (图片宽度))

这里第一字节偏移是17,因为PNG结构是8字节文件头加4字节块长度加4字节“IHDR”标识,第17个字节开始才是宽度。到整数按大端序解析即可,PNG标准里多字节整数都是大端,不需要手动翻转字节顺序。

6.2 反向封包:把修改过的图写回客户端

提取的最终目的是改图。改完图片后把它重新封装成客户端能读的格式,只需要把剥离下来的自定义头部再拼回去。做法是提取时把头部字节存到另一个变量里,修改完PNG后,用“头部字节集 + 新PNG数据”组合成新文件。注意头部字节集要在剥离前截取并保存,不要重复从原文件里读。反向封包的坑在于头部里往往带有文件偏移量或素材索引字段,图片尺寸变了这些字段就可能失效。要看具体客户端的读取算法,没有通用方案,只能对照之前保存的偏移记录逐个检查。验证回写是否成功,最直接的指标是客户端加载新文件不报错,且图片显示正常。

从那以后我每次做完批量提取,都会强制走一遍宽高对照和头部二次拼接测试,这个习惯帮我少踩了不少封装格式的坑,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进