上个月我帮一个朋友恢复他丢失的源工程,情况是这样的:他手头只有一个打好的微信小游戏包,加一个安卓的APK安装包,整个Cocos Creator 2.4的项目源文件因为硬盘故障全没了。我把这两个产物拆开看了半天,最后花了大概一天半,把场景、预制体、脚本代码和资源目录全部还原出来,重新在Creator里打开,项目能正常跑起来继续改需求。回来之后我把这套流程整理成了一个命令行工具,这篇文就详细聊聊它的设计思路、实操步骤和踩过的坑。
先说清楚这个工具解决的是什么问题:它面向Cocos Creator 2.4.x构建产物(微信小游戏、安卓APK、iOS安装包、Web版本均可),做的是把构建产物里残留的脚本代码、场景序列化数据、资源引用关系重新组装成一个可被Creator识别和打开的标准工程。这里有个前提说在前面:还原自己的项目资产、做学习研究是完全没有问题的,但拿去还原别人的商业游戏用于二次分发,既不合规也不是工具的设计目的。下面进入正题。
1. 为什么需要这样一把"还原钥匙":构建产物是最后的资产
1.1 我遇到的真实困境:源工程没了,只剩一堆构建物
很多团队做Cocos Creator项目时,源工程管理并不规范。我见过的情况包括:外包公司交付后原始工程没有同步、团队解散后代码仓库权限丢失、换电脑时Git记录损坏、甚至有人只把发布包发到客户那边,源工程在某个角落再也找不到了。
对Cocos Creator项目来说,源工程丢失最头疼的点不在于美术资源——资源一般还有原始图片和音频文件,真正难恢复的是三样东西:场景/预制体的节点结构、脚本组件的挂载关系、脚本代码本身。普通玩家看一个APK或者小游戏包,里面的内容就是一堆压缩混淆过的JS文件和二进制资源,觉得"这跟源码完全是两回事"。但做过逆向的人都知道,Cocos引擎的构建产物在还原这件事上,条件其实比很多人想象的友好。
1.2 2.4 构建产物里天然带着"半套工程"
为什么说友好?关键在于Cocos Creator的构建链路上两个核心事实:
第一,脚本并没有被编译成二进制字节码。TypeScript脚本在构建时被编译成JavaScript,然后通过webpack等工具打包合并成game.js(微信小游戏端)或类似的核心JS文件。既然是JS,就保留了变量名、函数体、类定义,只是经过压缩混淆之后可读性变差。这和C++编译成机器码、C#编译成IL后的还原难度完全不是一个量级。
第二,场景和预制体本质上是一份JSON序列化数据。Cocos Creator 2.4的场景文件(.scene)、预制体文件(.prefab)在编辑器里虽然是二进制格式存储(序列化后的JSON数组),但在构建产物里,它们的结构信息会以更直接的JSON形式出现在assets目录下。节点层级、组件属性、资源引用全都在,只是需要通过映射关系把"压缩uuid"还原成"完整uuid",再对应到真实的资源文件路径。
所以,只要把构建产物里的三类文件理顺:settings.js(元数据)、game.js(脚本代码)、场景/预制体JSON(结构数据),还原一个可编辑的工程是完全可行的。
1.3 工具定位于"恢复"而非"破解"
这款一键解析工具的核心目标是信息重组:把一份被打散的信息重新拼装起来。整个还原过程不涉及绕过任何加密算法,也不试图摘除任何防护机制,纯粹是把Cocos引擎已经公开的文件格式规范反向使用一遍。
我理解很多读者对"逆向"两个字有天然的兴奋感,但说句实在话,这个工具最大的价值场景就是你自己的项目炸了。把它想成"数据恢复工具",比"破解工具"更准确。工具在还原过程中也会自动丢弃一些不适合还原的内容,比如第三方付费插件混淆后的代码、明显有版权保护的资源包,这些我会在后面的合规边界里细说。
2. 还原链路上的三个关键关口
要写一个能用的还原工具,第一步不是写代码,而是吃透Cocos Creator 2.4构建产物的文件格式。我把整个还原链路拆成三个关口,任何一个理解不到位,输出工程就是坏的。
2.1 settings.js:整个工程的"文件系统元数据"
在微信小游戏构建产物里,src/settings.js是第一个要打开的文件。它本质上是一个给引擎运行时加载用的全局配置对象,但里面存着还原工程最需要的两张表。
第一张是mounts数组。每个bundle对应一条mount记录,包含bundle的id、根路径等信息。这告诉你工程里分了哪些bundle,主包在哪里,resources目录是否独立成包。
第二张是uuidMap对象。这是最关键的字典,key是压缩后的uuid,value是完整uuid。Cocos在序列化数据里为了减小体积,会把36位标准uuid压缩成很短的字符串(常见规则是去掉横线后取前5位,配合引擎内部的查表能力还原),场景JSON里自定义脚本组件的__type__用的就是这个压缩值。没有这张表,你根本不知道某个组件到底对应的是哪个脚本。
举个例子,settings.js里的核心结构长这样:
window.__settings = { platform: "wechatgame", version: "2.4.10", engine: "2.4.10", uuidMap: { "a1b2c": "d94e8a5d-3d29-4edc-ba2e-5f8c6aa51f62", // ... }, mounts: [ { id: "main", path: "main", root: "" }, { id: "resources", path: "resources", root: "resources" } ], // ... };如果把还原工程比作拼图,settings.js就是拼图盒背面的完整图案。工具启动后第一步永远是解析它,把uuidMap载入内存,后续所有场景解析都靠这张表做翻译。
2.2 game.js:被webpack打包压缩后的脚本集合
在微信小游戏产物里,game.js是把所有自定义脚本和引擎启动逻辑打包后的产物。Charset是纯JavaScript,但经过webpack处理之后,每个模块被包在一个函数作用域里,变量名被压缩成短名,类名通常还是保留着的(因为Cocos的组件系统需要通过类名和装饰器注册),但整体可读性很差。
还原工具在脚本这一关要做的不只是"把代码拿出来",而是要做三件事:
- 美化(beautify):把压缩成一行或几行的代码通过格式化工具还原成缩进清晰的代码,这步没有技术难点,但能极大提升后续阅读效率。
- 提取类定义与组件注册名:Cocos 2.4中自定义组件使用
cc._decorator或@ccclass装饰器注册,构建后代码里会留下cc._RF.push({}, "uuid", "ClassName", undefined)这样的注册痕迹。通过扫描这些注册调用,可以把脚本uuid、类名、代码片段三者锁定。 - 关联project.js里的scriptList:构建产物里还有一份
src/project.js,里面通常带scriptList,它是脚本uuid与类名的官方对照表。结合这份对照表,给每个脚本文件命名、确定它在assets目录里的相对路径,脚本还原的根基就稳了。
这一步的坑在于:不是每个类名都能恢复。如果项目里用了构建时的JS混淆插件,或开发时脚本名和@ccclass类名不一致,部分组件会变成无法识别的匿名类,工具只能按UnresolvedScript_<uuid>这样的占位名处理。
2.3 场景与预制体JSON:节点组件序列化结构
Cocos Creator 2.4的场景文件在编辑器里保存为二进制JSON数组,第一个元素是场景资源对象,后面的元素是节点、组件、数据对象,对象之间的引用靠{"__id__": N}指向数组下标。
构建产物里这些场景数据以解压后的assets资源出现,结构基本一致。看一个简化版的场景片段:
[ { "__type__": "cc.SceneAsset", "_name": "Main", "scene": { "__id__": 1 } }, { "__type__": "cc.Scene", "_name": "Main", "_children": [ { "__id__": 2 } ] }, { "__type__": "cc.Node", "_name": "Canvas", "_components": [ { "__id__": 3 } ], "_children": [ { "__id__": 4 } ], "_parent": { "__id__": 1 } }, { "__type__": "a1b2c", "_node": { "__id__": 2 }, "_enabled": true, "_name": "PlayerController" } ]注意这里__type__为"a1b2c"的组件,a1b2c就是压缩uuid。有了settings.js的uuidMap,工具可以把这个值换回完整uuid,再通过scriptList找到类名,最后还原出这个组件对应的脚本文件内容。
场景/预制体的还原是整套工具里逻辑最重的部分,因为Cocos的序列化格式里还存在大量内置组件类型(cc.Node、cc.Sprite、cc.Animation等),它们不需要脚本映射,但属性的字段名和默认值需要和Creator的import导入逻辑完全对齐,否则场景打开后会出现资源丢失或属性异常。
3. 工具核心模块拆解
把上面的原理落地成工具,我按职责拆成了五个模块。这里每个模块都说下设计思路和关键代码逻辑,不贴完整工程,重点讲清楚"为什么这么写"。
3.1 入口模块:解包与产物识别
入口模块负责把不同来源的构建产物统一成一个临时目录。微信小游戏产物本身是个文件夹,直接用;APK产物需要先解包,Android端的APK本质是个zip,我用apktool解包,也可以直接unzip拿到assets目录;iOS的ipa同理;网页版产物直接给目录路径即可。
解包完成后的第一件事是识别引擎版本。这一步决定了后续所有格式解析规则。Cocos Creator 2.x各个小版本之间序列化格式有细微差异,我用settings.js里的engine字段判断,如果是2.4.x就按2.4规则解析,版本不在支持范围内直接退出并给出提示。
这个模块还有一个容易被忽略的职责:过滤无用文件。构建产物里往往有大量引擎内置资源、自动生成的配置,还原工程不需要把全部东西抄回来,工具会按白名单机制只保留脚本、场景、预制体、动画、图集、音频、字体等核心资源,同时保留project.json和settings.js作为校验依据。
3.2 元数据与UUID引擎
所有需要"翻译"的地方,都会查这个模块。它在初始化时读入settings.js的uuidMap和scriptList,然后对外提供三个方法:
completeUuid(shortUuid):压缩uuid转完整uuidshortUuid(completeUuid):完整uuid转压缩uuid(生成meta和写回调脚本时要用)scriptNameByUuid(uuid):通过uuid拿类名,拿不到就返回占位名
生成.meta文件是元数据模块的另一项核心工作。Cocos Creator工程的每个资源旁边都有同名.meta文件,里面最重要的字段是uuid。还原出的工程要让Creator认得,必须给每个还原出的资源分配稳定uuid。工具的处理策略是:能查表还原的就用查到的完整uuid,查不到的按规则新生成一个,并在输出报告里标注。
3.3 脚本还原模块
脚本还原模块读入game.js,先用js-beautify做代码美化,再通过正则与AST扫描做两件事:提取cc._RF.push里的uuid、类名、路径信息;定位每个类的完整代码块并切割出来。
切割逻辑我做了两版。第一版用正则暴力匹配,速度快但对装饰器较多、代码嵌套深的类容易切错;第二版改用babel解析AST,按FunctionDeclaration和ClassDeclaration的边界切分,准确率高很多,代价是处理大文件时内存占用高。最终工具里默认走AST路线,失败回退到正则。
这一步的实测经验:微信小游戏包的game.js体量通常在1MB到5MB之间,AST解析大约几秒到十几秒,属于可接受范围。如果你在处理超大项目时感觉慢,可以先粗切再精扫,不要一上来就全量AST。
脚本还原后,工具按scriptList里的路径信息,把每个脚本写入assets/脚本目录/类名.ts(如果原本就是TS开发的)或.js。写入时会在文件头部加一行注释,标明原始构建来源和还原时间,方便日后追溯。
3.4 场景与预制体重建模块
这是整个工具里最容易出错的地方。场景文件还原不是简单地把JSON拷贝一份,而是要做三类转换:
- 引用转换:把
{"__id__": N}的数组下标引用保留下来(.scene格式本身就靠下标引用,可以不变),但需要按Creator导入时的规则重排对象顺序,否则编辑器会报"文件格式错误"。 - 压缩uuid替换:把自定义脚本组件的
__type__从压缩uuid替换成类名。这一步做完的场景才能在编辑器里正确关联脚本并显示组件名。Creator的序列化数据对脚本组件__type__在保存时也会写成类名,所以替换为类名反而更接近真正的源工程格式。 - 资源引用路径修正:场景里的Texture、SpriteFrame、Animation等资源,序列化数据里引用的是uuid或压缩uuid。工具会把这些引用改写成Creator工程资源系统中的路径引用,并确保对应资源已经拷贝到还原工程的assets目录下。
转换完成后,文件按assets/场景目录/场景名.scene的规则写入。Prefab的处理逻辑基本相同,统一走同一套转换管线。
4. 实操全流程:从构建产物到可打开的Creator工程
理论讲完,下面上实操。以一个微信小游戏构建产物为例,完整跑一遍还原流程。
4.1 准备阶段:需要准备的工具与环境
还原工具我选择了Node.js实现,理由很简单:要解析的game.js、settings.js都是JS,Node生态里解析、美化的库都是现成的。你本地需要准备:
- Node.js 14以上版本(建议16+,AST解析对内存有要求)
- 解包工具(APK用apktool或直接unzip,ipa用unzip)
- Cocos Creator 2.4.x编辑器(用于打开还原后的工程)
操作前先确认你的产物目录结构。微信小游戏构建产物一般长这样:
wechatgame/ ├── game.js ├── game.json ├── project.config.json ├── src/ │ ├── settings.js │ ├── project.js │ └── assets/ └── assets/ └── ...如果你的产物是APK,先解包:
apktool d game.apk -o game_apk # 或者直接解压只取assets unzip game.apk -d game_unzip解包后进入assets目录,你看到的src和assets就是构建产物的核心内容。
4.2 一键解析:命令与产出物
工具安装好后,运行方式很简单:
# 针对微信小游戏产物目录 cocos-restore -i ./wechatgame -o ./restored_project # 针对已解包的APK assets目录 cocos-restore -i ./game_apk/assets -o ./restored_project命令执行过程中,工具会在终端打印解析日志:
[INFO] 引擎版本: 2.4.10 [INFO] 解析 settings.js: 发现 1280 个 uuid 映射 [INFO] 解析 project.js: 脚本总数 86 [INFO] 切割 game.js: 提取脚本类 73 个 [INFO] 还原场景: Main.scene (节点 204 个) [INFO] 还原预制体: Player.prefab (节点 36 个) [INFO] 还原预制体: Bullet.prefab (节点 8 个) [WARN] 3 个脚本未找到对应类名, 已生成占位文件输出目录restored_project结构如下:
restored_project/ ├── assets/ │ ├── scenes/ │ │ ├── Main.scene │ │ └── Main.scene.meta │ ├── scripts/ │ │ ├── PlayerController.ts │ │ ├── PlayerController.ts.meta │ │ ├── BulletManager.ts │ │ └── ... │ ├── textures/ │ │ ├── bg.png │ │ ├── bg.png.meta │ │ └── ... │ └── resources/ └── project.json这一步值得注意:还原出的工程目前只是"文件结构正确",还没有通过Creator的导入验证,下一节会讲打开时遇到的状况。
4.3 在Creator 2.4里打开并修复报错
打开编辑器后,用"导入项目"选择还原目录。第一次打开几乎必然有报错,别慌,大部分报错是资源导入顺序导致的。我的经验是按快门(保存)+ 重新打开一两轮,Editor会把.meta重新索引一遍,很多问题会自动消失。
常见的几类报错和处理方式:
脚本类名缺失导致的组件丢失。表现是场景打开后某些节点缺少原先的脚本组件,Console里提示Unknown script。这时去assets/scripts下看占位脚本,确认是不是工具没能匹配上的那几个类。如果是开发时的类名和文件名不一致导致的识别失败,手动改一下占位脚本里的类名,在场景里重新挂载组件即可。
资源引用断裂。场景里某些图片显示为紫块。通常是纹理的uuid在构建产物里被重新压缩过,工具查表时没找到对应完整uuid。解决方式是把assets/scripts里的meta和资源meta一起删除,让Creator重新生成,然后手动把场景里的SpriteFrame引用拖回去。这种问题多集中在图集类资源。
脚本编译报错。还原出的TS脚本可能因为原项目的tsconfig配置、命名空间设置差异,在编辑器里编译不过。先查看project.json里的语言版本和构建设置,按源项目情况修正。如果原项目是纯JS开发,基本不会遇到这个问题。
修复完成的标准是:场景能正常打开,节点树完整,脚本组件挂载正确,点击预览能跑起来。到这里,一套"可用的源工程"就恢复了。
5. 还原质量的三个层级和常见坑
不是所有还原都能一次到位。根据还原结果的完整度,我把它分成三个层级,方便你评估自己项目的还原预期。
5.1 层级划分:可打开、可运行、可二次开发
| 层级 | 判断标准 | 通常能达到的条件 |
|---|---|---|
| 可打开 | Creator能导入工程,场景正常打开,无致命报错 | 产物完整,settings.js完好,场景文件未被篡改 |
| 可运行 | 编辑器预览出完整游戏,交互正常 | 脚本识别率高,资源引用完整,插件脚本不依赖特殊运行时 |
| 可二次开发 | 能在还原工程基础上直接改需求、加功能、重新发布 | 脚本类名全部恢复,代码可读性高,编辑器组件挂载无异常 |
我做过的还原项目里,大约八成能达到"可运行"层级,其中一半能顺利进入"可二次开发"。剩下的情况多半是构建时开启了代码混淆,或使用了定制构建插件,导致脚本类和场景组件的关联断链。
5.2 高频问题清单与处理方案
下面这张表是从多次还原实践中总结的高频问题,基本覆盖了九成以上的异常情况。
| 问题现象 | 根因 | 处理方案 |
|---|---|---|
| 场景里自定义组件全部丢失 | 自定义脚本被构建混淆,uuidMap无法匹配 | 打开project.js手动对齐scriptList,按组件出现位置补挂 |
| 脚本文件还原但代码顺序错乱 | 大文件AST切割超时降级到正则,边界切错 | 调整内存限制强制走AST,或按类名手动从game.js提取 |
| 图集资源全部失败 | 合并图集在构建时被压缩成大图+plist,工具没找到plist映射 | 补充图集plist解析逻辑,将大图裁回碎图并生成meta |
| 场景里节点顺序错乱 | 序列化数组重排规则写错 | 检查数组第一项索引偏移,按Creator编辑器实际导入规则重排 |
| 插件脚本还原后无法运行 | 第三方插件代码依赖编辑器API,脱离编辑器后失效 | 工具只提取文件不重组逻辑,插件需从官方渠道重装 |
5.3 插件脚本、AssetBundle与分包的特殊处理
插件脚本(比如原生SDK适配、广告组件)在构建产物里和普通脚本不同,它们通常以插件脚本标记存在于project.js里,代码不被webpack打包,直接以单文件形式出现在assets目录下。还原时工具会识别这类文件并原样拷贝,不参与类名切割。但如果插件脚本里引用了原生Android/iOS代码,那部分是无法从JS产物还原的,只能到原生工程目录里找。
AssetBundle和主包在2.4里的处理方式也有区别。AssetBundle构建出的bundle目录有自己独立的settings.js,里面同样携带着自己的uuidMap。工具在还原时如果遇到bundle,会递归调用同样的解析流程,把bundle里的资源和脚本也还原出来,并在主工程的meta里建立引用关系。分包(微信小游戏分包)的处理逻辑类似,关键在于各个包的settings.js不能被遗漏。
6. 工具边界、合规红线与实用建议
6.1 哪些项目适合还原,哪些不建议折腾
经过多次实测,我认为适合还原的项目特征很明确:Cocos Creator 2.4.x开发、构建时未开启高强度代码混淆、源工程使用的第三方组件不多。这类项目还原成功率最高,产出也最有价值。
反之,这三种情况不建议折腾:一是构建时用了自定义的加密插件、对game.js整体加密过的,还原难度指数级上升;二是项目重度依赖付费插件且插件在构建后功能性失效的,还原出来也跑不起来;三是只想要素材不想要工程的情况,直接去assets目录拿原图更快,没必要跑完整还原。
6.2 关于合规:还原自己拥有的项目
这是所有逆向相关话题都绕不开的部分。我的观点很明确:工具的使用边界是你对目标产物拥有合法权利,包括:你自己的项目备份恢复、你所在公司内部的项目资产抢救、经版权方明确授权的学习研究。它不应该被用于解除他人游戏的保护机制、移除广告、偷窃商业素材或代码。
我在工具里也做了一层防护:还原过程中如果检测到资源或脚本带有明显的外源版权标记,或者工程里存在付费插件授权校验代码,工具会在日志里明确提示,并跳过这部分内容的还原。意料之外的收获是,这层防护反而让工具在公司内部推进时少了很多阻力——它天然就是"恢复资产"的工具,而不是"抄代码"的工具。
6.3 后续可扩展方向
这套工具目前只是我个人的半成品,后续还有很多值得做的方向:
- 代码语义恢复:从还原出的JS反推更接近TS的写法(类型推导、装饰器补全),让代码可读性直接对齐源工程开发体验。
- 依赖关系图生成:扫描脚本之间的require/import关系,自动生成模块依赖树,帮开发者快速理解还原工程的结构。
- 资源清理与压缩检测:识别构建产物里的冗余资源、引用计数为零的资源,给出清理建议,这一步对还原后的工程质量很有价值。
- 多版本引擎适配:把2.4的解析规则抽象成配置化,向后兼容3.x的序列化格式。3.x的管线变化很大,目前单独维护一套解析器成本较高,但值得投入。
最后说一句我的个人经验:还原工程这件事,最忌讳的就是贪多求全。拿到产物后先不要急着跑工具,花半小时把settings.js里的uuidMap、project.js里的scriptList、构建时的引擎版本看清楚,能为你省下后面好几个小时的排错时间。工具能自动完成的事情是"翻译",但关键的决定权始终在你手上——每一份还原成功、能在编辑器里重新打开的工程,背后都是你对项目结构本身的理解在起作用。