封包的结构比较清晰,只是其中的图像是经过压缩编码的。
图像分为两种,一种是文件标识为 “GE” 的单图文件,另一种则是文件标识为 “PGD3” 的差分文件。差分文件中含有一个单图文件的文件名,当引擎读取差分文件时会同时读取单图文件,解码后将两者进行混合。代码里还备有 “PGD2” 的分支,是 “PGD3” 的一个子集。不过这个游戏里似乎并没有该类型的文件,所以先不管了。
两种图像格式的结构
struct GEHeader { char magic[2]; // "GE" uint16_t size; uint32_t x; uint32_t y; uint32_t width; uint32_t height; uint32_t display_width; uint32_t display_height; uint8_t comparess; uint8_t zero[3]; }; struct PGD3Header { char magic[4]; // "PGD3" uint16_t offset_x; uint16_t offset_y; uint16_t width; uint16_t height; uint16_t component; char name[34]; }
根据 GE 文件头中的压缩指示字段的不同有 3 种不同的压缩方式,该游戏中的图像对应压缩的字段都是 3,而后根据解码出来图像的颜色深度不同,也对应着不同的处理方式。当然在内存中两者最终都会解码为 32 位 BGRA 的格式,而且会将图像的宽度向上对齐到 2^n。以上就是这个解码过程的概况了,步骤还是比较多的,而且代码也散落在好几个子函数里。虽然我觉得应该把算法全逆出来才算完整,不过我还是没法理解算法本身的内容。。。就算是逆估计也就是对着代码翻译或是直接 F5,这就是纯体力活了,所以还是算了(((((((・・;)
于是愉快地决定直接去内存里 dump 现成数据。而引擎解码对应的图片是调用了导出的 PalSpriteLoad,传递的关键参数就是资源文件名了,这使得处理变的更加简单。根据前面的分析,在解封包的时候就可以同时判断图片的类型(是不是差分),以及 GE 文件的压缩方式等,这些数据可以记录下来,随后传递给调试器即可根据文件类型自动收取对应文件的数据了。
另外脚本文件还有一小段的解密过程
以上就是 PAL 引擎(?)封包的大致情况了。