Cocos Creator游戏资源逆向提取实战:从APK解密到PNG转换

📅 2026/7/11 6:11:54 👁️ 阅读次数 📝 编程学习
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的渲染格式,比如PVRTC(iOS)或ETC(Android)。一张PNG图片在构建时,会被转换成对应的.pvr.ccz.etc.ccz文件。这个.ccz后缀,代表它经过了zlib压缩。所以,你看到的资源文件,是“特定GPU格式 + 通用压缩”后的复合体。

资源加密则是可选的构建选项。开发者可以勾选“加密资源”选项,Cocos Creator会使用一个密钥对资源文件进行XXTEA加密。加密后的文件,没有密钥根本无法读取其原始内容。这就是为什么你直接打开这些文件是一堆乱码。

因此,完整的逆向提取流程必须逆向这两个过程:

  1. 解包APK:获取原始的、经过处理和加密的资源文件。
  2. 定位关键文件:找到记录资源列表和加密密钥的配置文件。
  3. 解密资源:如果资源被加密,使用密钥进行解密,得到压缩后的纹理数据。
  4. 解压与格式转换:解压.ccz文件,得到原始的纹理数据(如PVR、ETC数据),再将这种GPU纹理数据转换回标准的PNG或JPG图片。

2.2 工具包详解与选型理由

工欲善其事,必先利其器。下面这个工具包是我经过多次实践筛选出来的稳定组合,每个工具都有其不可替代的作用。

1. Apktool

  • 作用:反编译APK文件,获取完整的资源文件(包括assets目录)、清单文件(AndroidManifest.xml)和Smali代码。
  • 为什么是它?相比直接使用压缩软件解压.apk(重命名为.zip),Apktool能更规范地处理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等文件并预览、导出。
  • PVRTexTool:Imagination Technologies(PowerVR 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到PNG

3.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.jsongame.json。用文本编辑器打开它。这个文件通常包含两个至关重要的部分:

  1. assets:一个庞大的JSON对象,键是资源在游戏中的唯一ID(通常是路径的MD5哈希),值包含了资源的真实路径、类型、以及一个md5校验值。
    "a0b1c2d3e4f56789": { "path": "textures/ui/button.png", "type": "png", "md5": "e10adc3949ba59abbe56e057f20f883e" }
  2. encryptedkey:如果资源被加密,这里会标明。encrypted可能是一个布尔值true,也可能是一个数组,列出了所有被加密的资源ID。key则是加密密钥的Base64编码字符串。这是提取资源的“命门”
    "encrypted": true, "key": "aGVsbG8gd29ybGQhISE="

实操心得:有时config.json本身可能被简单编码(如异或)。如果直接打开是乱码,可以尝试用十六进制编辑器查看文件开头,或者用Python尝试几种常见的编码方式。一个明显的标志是,正常的JSON以{开头。

3.2 第二步:解析配置与构建资源映射

找到了config.json,下一步就是写一个Python脚本来解析它。这个脚本的核心任务是建立两个映射关系:

  1. 资源ID到真实文件路径的映射config.json里的assets表,其键(ID)对应的文件,实际存放在assets目录下的某个位置,文件名通常就是这个ID(可能没有后缀,或加上了.ccz等后缀)。你需要遍历assets目录(排除config.json本身),找到这些文件。
  2. 资源ID到原始工程路径的映射:通过assets表里每个ID对应的path值,你就知道这个文件在Cocos Creator原始项目里应该是什么路径和名字。

这里有个关键点:资源文件的实际存储名,通常是其ID,或者ID加上格式后缀。例如,ID为a0b1c2d3e4f56789的PNG资源,在assets目录下可能是一个名为a0b1c2d3e4f56789的无后缀文件,也可能是一个名为a0b1c2d3e4f56789.pvr.ccz的文件。

脚本的第一步是加载并解析config.json

import json import os config_path = os.path.join('game_unpacked', 'assets', 'config.json') with open(config_path, 'r', encoding='utf-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_info(ID->信息)和asset_files(ID->实际文件路径)。通过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(f"CCZ 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 # 可能已经是未压缩的纹理数据(如.pvr)

3.4 第四步:纹理格式转换与图片导出

经过解密和解压,你现在拿到了原始的纹理数据,比如PVRTC或ETC格式的数据块。这些数据不能被普通图像软件识别,需要转换。

1. 识别纹理格式你需要检查数据头来判断格式。常见的格式有:

  • PVR:文件头通常以0x03525650(小端序,即PVR\x03) 开始。头结构里包含了像素格式(PVRTC4, PVRTC2, RGBA8888等)、宽度、高度。
  • ETC:没有统一的文件头,但Cocos Creator生成的ETC文件通常也带有一个简单的头。有时需要依赖config.json里资源的type字段(可能是png,但实际是pvretc)或尝试解析。

一个简单的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转换对于某些格式(如未压缩的RGBA8888),Pillow可以直接从字节数据创建图像。但对于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用于PVR,pyetc用于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_ok=True) 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 0x9C0x78 0xDA(zlib头标志)。
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.jsonassets表中,如果一个资源的typeplist,或者它的路径以.plist结尾,那么它很可能是一个图集配置文件。同时,会有一个同名的图片资源(类型为png等,但实际是.pvr.ccz)与之对应。

拆解图集步骤:

  1. 导出图集大图:按照前面的流程,先导出图集对应的纹理图片(如ui_atlas.pvr.ccz->ui_atlas.png)。
  2. 解析plist文件:plist文件可能是XML格式或二进制格式。你可以使用Python的plistlib库(针对XML格式)来解析。关键信息在frames字典里,每个子图都有一个字典,包含:
    • frame: 字符串如{{x, y}, {width, height}},表示子图在大图中的矩形区域。
    • rotated: 布尔值,是否被旋转。
    • offset,sourceSize等(可能用不到)。
  3. 使用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, x+w, y+h)) if rotated: sub_img = sub_img.rotate(-90, expand=True) # 通常需要逆时针旋转90度来纠正 # 保存,frame_name可能是带路径的,如"button/ok.png" sub_img.save(os.path.join('extracted_frames', frame_name))

注意事项:有些plist文件可能是二进制格式(bplist),plistlib可能无法直接读取。这时可以尝试在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版本的常见情况,但遇到特别定制或修改过的版本,可能还需要你根据上面的排查思路,动手修改脚本中的解析逻辑。记住,逆向的本质是理解和模仿原始工具的行为,多看二进制,多验证中间结果,问题总能解决。