Cocos Creator游戏资源逆向提取实战:从APK解密到PNG转换
1. 项目概述从APK到游戏美术资产的完整提取最近在分析一个用Cocos Creator 2.4.x版本开发的老游戏想把它里面的美术资源完整地扒出来。这活儿听起来像是“破解”但在游戏开发、美术学习、甚至是同人创作里其实是个挺常见的需求。比如你想研究一下某个独立游戏的美术风格或者想用它的素材做个非商业的粉丝作品第一步就是把资源从打包好的APK里弄出来。Cocos Creator打包后的资源尤其是图片并不是像PNG、JPG那样直接扔在文件夹里而是经过了一系列的加密、压缩和格式转换直接解压APK是看不到原图的。这个实战过程就是一步步拆解这个“黑盒”把游戏里的精灵图、UI贴图、背景图等资源无损地还原成可编辑的图片文件。整个过程的核心思路可以概括为“解包、定位、解密、转换”四步。首先你需要一个能解开Android安装包的工具把APK的壳子扒掉看到里面的原始文件结构。然后在那一堆看似杂乱的文件里找到Cocos Creator存放资源的“保险箱”和“钥匙”。接着用正确的“钥匙”去打开“保险箱”把里面加密过的、压缩过的二进制数据读出来。最后把这些二进制数据根据Cocos Creator特定的格式规则重新拼装成一张张完整的图片。听起来有点复杂但跟着步骤走一遍你会发现每个环节都有成熟的工具和清晰的逻辑。我会把用到的所有工具打包好你只需要按图索骥就能搞定。2. 核心思路与工具链解析2.1 逆向流程总览与原理为什么不能直接解压APK因为Cocos Creator为了优化游戏加载速度和保护资源在构建Build时会对资源进行处理。对于图片资源主要经历两个关键步骤纹理压缩和资源加密。纹理压缩是为了适配移动端GPU的渲染格式比如PVRTCiOS或ETCAndroid。一张PNG图片在构建时会被转换成对应的.pvr.ccz或.etc.ccz文件。这个.ccz后缀代表它经过了zlib压缩。所以你看到的资源文件是“特定GPU格式 通用压缩”后的复合体。资源加密则是可选的构建选项。开发者可以勾选“加密资源”选项Cocos Creator会使用一个密钥对资源文件进行XXTEA加密。加密后的文件没有密钥根本无法读取其原始内容。这就是为什么你直接打开这些文件是一堆乱码。因此完整的逆向提取流程必须逆向这两个过程解包APK获取原始的、经过处理和加密的资源文件。定位关键文件找到记录资源列表和加密密钥的配置文件。解密资源如果资源被加密使用密钥进行解密得到压缩后的纹理数据。解压与格式转换解压.ccz文件得到原始的纹理数据如PVR、ETC数据再将这种GPU纹理数据转换回标准的PNG或JPG图片。2.2 工具包详解与选型理由工欲善其事必先利其器。下面这个工具包是我经过多次实践筛选出来的稳定组合每个工具都有其不可替代的作用。1. Apktool作用反编译APK文件获取完整的资源文件包括assets目录、清单文件AndroidManifest.xml和Smali代码。为什么是它相比直接使用压缩软件解压.apk重命名为.zipApktool能更规范地处理Android资源编译后的二进制文件如resources.arsc确保assets文件夹里的文件被完整、正确地提取出来避免出现文件损坏。这是整个流程的基石。使用方式命令行工具。基本命令是java -jar apktool.jar d -f 你的游戏.apk -o output_folder。2. Python 3.x 相关库作用编写核心的解密和提取脚本。Python的跨平台性和丰富的库支持使其成为自动化这类任务的首选。关键库os,json,hashlib用于文件操作、解析配置文件、计算MD5用于验证文件。struct用于解析二进制文件头判断文件类型。zlib用于解压.ccz文件。PIL (Pillow)最终的图像处理库用于将纹理数据转换为PNG。为什么是Python灵活性极高。我们需要根据config.json动态决定解密密钥、遍历复杂目录、处理多种纹理格式用Python写脚本是最直接高效的。3. TexturePacker 或 PVRTexTool (可选但推荐)作用查看和转换纹理数据。虽然最终脚本会用Pillow完成转换但在调试阶段一个可视化的纹理查看器至关重要。TexturePacker商业软件但功能强大支持直接打开.pvr,.etc等文件并预览、导出。PVRTexToolImagination TechnologiesPowerVR GPU厂商官方提供的免费工具专门处理PVR格式非常权威。为什么需要它们当你解密解压出一个.pvr文件却无法用普通图片查看器打开时这些工具能帮你确认文件是否有效以及它的具体格式如PVRTC4, PVRTC2, RGBA8888等为后续编写转换脚本提供关键信息。4. 十六进制编辑器 (如 HxD, 010 Editor)作用手动分析文件格式验证猜想。这是逆向工程的“显微镜”。使用场景查看config.json文件是否被额外编码查看资源文件的文件头确认它是.ccz、.pvr还是别的什么验证解密后的数据是否正确。为什么需要脚本不是万能的。当脚本运行出错时你需要直接查看二进制数据来定位问题比如密钥是否正确、文件头是否损坏。注意工具获取与版本Apktool需要从其官网或GitHub发布页下载最新版。Python库使用pip install pillow即可安装Pillow。请避免使用来路不明的“破解版”或“一键打包工具”它们可能包含恶意代码或版本过旧导致处理失败。本文附带的工具包将包含已验证可用的Apktool版本和Python脚本模板。3. 实战步骤详解从APK到PNG3.1 第一步APK解包与资源定位拿到APK后第一步是把它拆开。打开命令行切换到你的工具目录执行java -jar apktool_2.7.0.jar d -f game_release.apk -o game_unpacked这条命令会强制-f将game_release.apk解包到game_unpacked文件夹。解包完成后进入game_unpacked/assets目录。这里就是游戏资源的藏身之处。Cocos Creator 2.x 版本通常会有以下关键结构assets/ ├── src/ # 游戏的JavaScript代码如果未加密 ├── res/ # 原始资源目录构建后内容会变化 ├── config.json (或 game.json) # **核心文件**资源清单和加密配置 ├── project.json # 项目配置文件 └── internal/ # 内部资源你的目标首先是找到config.json或game.json。用文本编辑器打开它。这个文件通常包含两个至关重要的部分assets一个庞大的JSON对象键是资源在游戏中的唯一ID通常是路径的MD5哈希值包含了资源的真实路径、类型、以及一个md5校验值。a0b1c2d3e4f56789: { path: textures/ui/button.png, type: png, md5: e10adc3949ba59abbe56e057f20f883e }encrypted和key如果资源被加密这里会标明。encrypted可能是一个布尔值true也可能是一个数组列出了所有被加密的资源ID。key则是加密密钥的Base64编码字符串。这是提取资源的“命门”。encrypted: true, key: aGVsbG8gd29ybGQhISE实操心得有时config.json本身可能被简单编码如异或。如果直接打开是乱码可以尝试用十六进制编辑器查看文件开头或者用Python尝试几种常见的编码方式。一个明显的标志是正常的JSON以{开头。3.2 第二步解析配置与构建资源映射找到了config.json下一步就是写一个Python脚本来解析它。这个脚本的核心任务是建立两个映射关系资源ID到真实文件路径的映射config.json里的assets表其键ID对应的文件实际存放在assets目录下的某个位置文件名通常就是这个ID可能没有后缀或加上了.ccz等后缀。你需要遍历assets目录排除config.json本身找到这些文件。资源ID到原始工程路径的映射通过assets表里每个ID对应的path值你就知道这个文件在Cocos Creator原始项目里应该是什么路径和名字。这里有个关键点资源文件的实际存储名通常是其ID或者ID加上格式后缀。例如ID为a0b1c2d3e4f56789的PNG资源在assets目录下可能是一个名为a0b1c2d3e4f56789的无后缀文件也可能是一个名为a0b1c2d3e4f56789.pvr.ccz的文件。脚本的第一步是加载并解析config.jsonimport json import os config_path os.path.join(game_unpacked, assets, config.json) with open(config_path, r, encodingutf-8) as f: config json.load(f) assets_info config.get(assets, {}) encrypted config.get(encrypted, False) encryption_key config.get(key, ) if encryption_key: # 密钥通常是Base64编码的需要解码成字节 import base64 encryption_key base64.b64decode(encryption_key)接下来你需要扫描assets目录建立实际文件列表。注意要跳过目录和非资源文件。def find_asset_files(assets_dir): asset_files {} for root, dirs, files in os.walk(assets_dir): for file in files: if file config.json or file project.json: continue file_path os.path.join(root, file) # 获取不带后缀的basename这很可能就是资源ID file_id os.path.splitext(file)[0] # 处理可能的双重后缀如 .pvr.ccz if file_id.endswith(.pvr) or file_id.endswith(.etc): file_id os.path.splitext(file_id)[0] asset_files[file_id] file_path return asset_files现在你有assets_infoID-信息和asset_filesID-实际文件路径。通过ID将它们关联起来你就知道哪个文件对应游戏里的哪个资源了。3.3 第三步资源解密与解压核心算法这是技术核心。流程是读取文件 - 判断是否加密 - 若加密则解密 - 判断是否是.ccz压缩 - 若压缩则解压 - 得到原始纹理数据。1. 解密算法 (XXTEA)Cocos Creator 2.x 使用的加密算法是XXTEA。你需要实现或找到一个XXTEA的解密函数。这里提供一个Python的实现示例注意密钥需要是16字节import struct def decrypt_xxtea(data, key): XXTEA 解密算法 if len(key) ! 16: raise ValueError(XXTEA key must be 16 bytes long) # 将数据转换为无符号整数列表 def to_ints(v): n len(v) # 数据长度必须是4的倍数不足补零 if n % 4 ! 0: v b\0 * (4 - n % 4) return struct.unpack(f{len(v)//4}I, v) def from_ints(v): return struct.pack(f{len(v)}I, *v) # 标准的XXTEA解密核心逻辑 # 此处省略具体的XXTEA算法实现代码因其较长。 # 在实际工具包中我会提供一个经过测试的、完整的 xxtea_decrypt 函数。 # 你需要确保这个函数接收 bytes 类型的 data 和 key返回解密后的 bytes。 decrypted_data xxtea_decrypt(data, key) # 假设这是已实现的函数 return decrypted_data在脚本中对每个文件操作with open(actual_file_path, rb) as f: file_data f.read() if encrypted and file_needs_decryption(file_id, config): # 需要判断该文件是否在加密列表中 try: file_data decrypt_xxtea(file_data, encryption_key) except Exception as e: print(f解密失败 {file_id}: {e}) # 有时可能只有部分资源加密跳过继续2. 解压算法 (.ccz).ccz文件是zlib压缩数据但前面有一个4字节的小端序头部表示未压缩的数据大小。import zlib def decompress_ccz(data): 解压 .ccz 格式数据 if len(data) 4: raise ValueError(CCZ data too short) # 前4字节是未压缩大小 uncompressed_size struct.unpack(I, data[:4])[0] # 剩余部分是zlib压缩数据 compressed_data data[4:] try: # wbits -15 表示使用原始deflate数据流无头部 decompressed_data zlib.decompress(compressed_data, -15) except zlib.error as e: raise ValueError(fCCZ decompression failed: {e}) if len(decompressed_data) ! uncompressed_size: print(f警告: 解压后大小不匹配。预期{uncompressed_size}, 实际{len(decompressed_data)}) return decompressed_data判断文件是否为.ccz可以通过检查文件头前4个字节后的魔数或简单地通过后缀/尝试解压来判断。# 尝试解压如果失败则可能不是ccz格式 try: file_data decompress_ccz(file_data) is_compressed True except: is_compressed False # 可能已经是未压缩的纹理数据如.pvr3.4 第四步纹理格式转换与图片导出经过解密和解压你现在拿到了原始的纹理数据比如PVRTC或ETC格式的数据块。这些数据不能被普通图像软件识别需要转换。1. 识别纹理格式你需要检查数据头来判断格式。常见的格式有PVR文件头通常以0x03525650(小端序即PVR\x03) 开始。头结构里包含了像素格式PVRTC4, PVRTC2, RGBA8888等、宽度、高度。ETC没有统一的文件头但Cocos Creator生成的ETC文件通常也带有一个简单的头。有时需要依赖config.json里资源的type字段可能是png但实际是pvr或etc或尝试解析。一个简单的PVR头解析示例def parse_pvr_header(data): 解析PVR文件头返回格式、宽度、高度等信息 if len(data) 52: # PVRv3头长度至少52字节 return None magic, flags, pixel_format, color_space, channel_type, height, width, depth struct.unpack(IIIIIIII, data[:32]) if magic ! 0x03525650: # PVR\x03 return None # 根据 pixel_format 判断具体类型 # 0xC PVRTCI_4BPP_RGB, 0xD PVRTCI_4BPP_RGBA, 等等 return {format: pixel_format, width: width, height: height, data_offset: 52} # 数据通常在头之后2. 使用Pillow转换对于某些格式如未压缩的RGBA8888Pillow可以直接从字节数据创建图像。但对于PVRTC、ETC等压缩纹理Pillow原生不支持。这时有两种途径途径A使用外部工具转换用Python调用命令行工具如PVRTexToolCLI进行转换再将结果读回。这更可靠但依赖外部工具。import subprocess # 将解压后的数据写入临时.pvr文件 temp_pvr temp.pvr with open(temp_pvr, wb) as f: f.write(texture_data) # 调用PVRTexTool转换为PNG subprocess.run([PVRTexToolCLI, -i, temp_pvr, -d, temp.png, -f, r8g8b8a8]) # 用Pillow读取PNG img Image.open(temp.png)途径B使用纯Python库解码寻找一些针对特定格式的Python解码库如pypvr用于PVRpyetc用于ETC。这些库可能不完善或难以安装但更集成。在附带的工具包中我会提供一个折中方案对于常见格式使用一个轻量级的、针对Cocos Creator常用格式调整过的解码模块。假设我们有一个函数convert_texture_to_image(raw_data, width, height, format)能返回一个PIL Image对象那么导出就很简单了from PIL import Image texture_info parse_texture_header(file_data) # 综合判断PVR/ETC等 if texture_info: img convert_texture_to_image( file_data[texture_info[data_offset]:], # 纹理数据部分 texture_info[width], texture_info[height], texture_info[format] ) # 根据 config.json 中的原始路径保存 original_path assets_info[file_id][path] output_path os.path.join(extracted_output, original_path) os.makedirs(os.path.dirname(output_path), exist_okTrue) img.save(output_path) print(f已导出: {original_path}) else: print(f无法识别的纹理格式: {file_id})4. 常见问题排查与深度技巧4.1 资源提取失败问题诊断即使按照步骤操作也可能会遇到各种问题。下面是一个快速诊断表问题现象可能原因排查步骤Apktool解包失败1. APK已加固加壳。2. Apktool版本过旧。3. Java环境问题。1. 使用file命令或查壳工具检查APK是否被加固如梆梆、爱加密。加固APK需要先脱壳这超出了本文范围。2. 更新到最新版Apktool。3. 确保命令行中Java可执行。找不到config.json1. 文件被重命名如game.json。2. 文件被移动到其他目录如assets/import。3. 文件本身被加密或混淆。1. 在assets目录下搜索所有.json文件。2. 使用find . -name *.json命令全局搜索。3. 用十六进制编辑器查看疑似文件看开头是否有{或可读的JSON片段。解密失败数据仍为乱码1. 密钥错误或未找到。2. 加密算法不是标准XXTEA。3. 只有部分文件被加密但脚本尝试解密了所有文件。1. 确认config.json中的key字段存在且正确Base64解码。2. 检查Cocos Creator版本。极老或修改过的版本可能用不同算法。3. 检查encrypted字段是布尔值true还是数组。如果是数组只解密列表内的ID。解压.ccz失败1. 文件实际上不是.ccz格式。2. 文件已损坏或解密不正确。3. 压缩方式有变如LZ4。1. 用十六进制编辑器查看文件头。.ccz前4字节是未压缩大小紧接着应是0x78 0x9C或0x78 0xDAzlib头标志。2. 先确保解密步骤正确。3. Cocos Creator默认用zlib但理论上可改。转换后的图片全黑或错乱1. 纹理格式识别错误。2. 宽度/高度解析错误。3. 数据偏移量计算错误。1. 用TexturePacker或PVRTexTool直接打开解密解压后的文件确认其实际格式。2. 核对解析头部的代码确保字节序大端/小端正确。3. 确认跳过了正确的头部长度再提取图像数据。导出的图片数量远少于预期1. 资源使用了图集Sprite Atlas。2.config.json中的assets表不完整。3. 脚本的文件名匹配逻辑有误。1. 检查导出的图片中是否有包含多个小图的大图。需要额外步骤拆图集见下文技巧。2. 有些资源可能通过其他方式加载未列入assets。3. 调试脚本打印出匹配到的ID和路径检查映射关系。4.2 高级技巧处理图集与plist文件Cocos Creator大量使用纹理图集来优化渲染性能。你可能会发现导出了很多张很大的图片但游戏里实际显示的是这些小元素。这是因为大图是图集而每个小元素的位置信息保存在一个.plist或.json文件里。如何识别图集在config.json的assets表中如果一个资源的type是plist或者它的路径以.plist结尾那么它很可能是一个图集配置文件。同时会有一个同名的图片资源类型为png等但实际是.pvr.ccz与之对应。拆解图集步骤导出图集大图按照前面的流程先导出图集对应的纹理图片如ui_atlas.pvr.ccz-ui_atlas.png。解析plist文件plist文件可能是XML格式或二进制格式。你可以使用Python的plistlib库针对XML格式来解析。关键信息在frames字典里每个子图都有一个字典包含frame: 字符串如{{x, y}, {width, height}}表示子图在大图中的矩形区域。rotated: 布尔值是否被旋转。offset,sourceSize等可能用不到。使用Pillow裁剪根据frame信息从大图中裁剪出每个子图。如果rotated为真还需要进行相应的旋转通常是顺时针90度。from PIL import Image import plistlib # 加载图集大图 atlas_img Image.open(ui_atlas.png) # 解析plist with open(ui_atlas.plist, rb) as f: plist_data plistlib.load(f) # 如果是XML格式 frames plist_data[frames] for frame_name, frame_info in frames.items(): # 解析frame字符串例如{{10,20},{30,40}} # 这里需要写一个解析函数得到x, y, w, h x, y, w, h parse_frame_string(frame_info[frame]) rotated frame_info.get(rotated, False) # 裁剪 sub_img atlas_img.crop((x, y, xw, yh)) if rotated: sub_img sub_img.rotate(-90, expandTrue) # 通常需要逆时针旋转90度来纠正 # 保存frame_name可能是带路径的如button/ok.png sub_img.save(os.path.join(extracted_frames, frame_name))注意事项有些plist文件可能是二进制格式bplistplistlib可能无法直接读取。这时可以尝试在Mac系统上用plutil命令转换或使用第三方库如biplist。另外Cocos Creator 2.4 也支持.json格式的图集配置其结构类似解析起来更简单。4.3 资源整理与工程还原展望当你成功提取出所有图片后得到的可能是一堆以MD5命名的文件或者保持了原始路径但散落在各处的文件。为了真正“还原”资源你可以利用之前解析config.json时建立的映射关系资源ID - 原始路径将所有提取出的图片按照原始路径整理到对应的文件夹中。这样你就得到了一个和Cocos Creator项目assets目录结构非常相似的资源文件夹。更进一步如果你还提取出了JavaScript源码如果未加密或已解密你甚至可以尝试在Cocos Creator中创建一个空项目将这些资源和代码放入对应位置进行有限度的“工程还原”。这对于学习游戏架构、复用某些特效或动画逻辑非常有帮助。当然这涉及到代码解密、适配引擎版本等更复杂的问题属于逆向工程的深水区了。整个提取过程最考验耐心的是对二进制格式的调试和异常情况的处理。我提供的工具包里的Python脚本已经处理了大多数Cocos Creator 2.4.x版本的常见情况但遇到特别定制或修改过的版本可能还需要你根据上面的排查思路动手修改脚本中的解析逻辑。记住逆向的本质是理解和模仿原始工具的行为多看二进制多验证中间结果问题总能解决。