逆向学习:从已上线微信小游戏反推Cocos工程结构(以切水果跑酷为例)
微信小游戏跑在浏览器环境里,它的 JavaScript 代码和资源文件本质上都是“半公开”的。就算没有源码,只要把发布包拿到手,就能反向还原出开发者当初是怎么搭工程的。我拿一个切水果跑酷类的小游戏当案例,把整条逆向分析链路走了一遍——从获取小游戏包、解密资源、反推代码逻辑,到最终还原出 Cocos Creator 的工程结构,整个过程折腾了大概两天。这篇文章把每一步的关键点和踩过的坑都记录下来,希望对打算做竞品分析或者想研究 Cocos 底层机制的开发者有点帮助。
切水果跑酷这种玩法在微信小游戏里非常典型,它同时涉及 2D UI 交互、3D 场景渲染、物理碰撞、资源动态加载等多个模块,拿它当逆向样本,基本上能把 Cocos 工程里 90% 的核心结构摸个遍。不管你是 Cocos 新手还是已经跑过几个项目的熟手,这篇文章能帮你建立的是一套“从编译产物反推源码工程”的方法论,以后不管拿到什么小游戏,都能按这套思路去拆解。
1. 逆向学习的目标与整体思路
1.1 为什么要拿已上线小游戏做逆向分析
先说清楚动机。很多人一听“逆向”就想到灰产破解,实际上在游戏开发领域,逆向分析更多是用于竞品调研、技术选型参考、性能瓶颈排查和个人学习。我这次做逆向,目标很明确:想弄清楚一个完整上线的 Cocos 小游戏,它的工程目录是怎么组织的、场景和预制体是怎么设计的、资源加载策略是什么、代码逻辑如何分层。这些信息在官方文档和教程里永远学不到,只有拆开真实项目才能看清楚。
拿切水果跑酷这个品类举例,它表面看起来玩法简单,但实际上要处理好水果切割的物理反馈、跑酷路线的动态生成、分数和连击的 UI 表现、音效和动画的同步播放。任何一个模块的处理方式,都反映了开发团队当初的架构决策。通过逆向反推,等于把他们的决策过程倒放一遍,这对提升自己的项目架构能力帮助非常大。
1.2 逆向必须搞清楚的三件事:摸清引擎、定位资源、读懂代码
整个逆向过程可以拆成三个层次,我建议所有想入门的人先把这个框架记到脑子里。
首先是确定引擎类型和版本。微信小游戏运行时不直接暴露引擎信息,但方法很多:先看包体里的 adapter 文件、JS 文件名和文件头特征,再看首包代码里的引擎全局变量——Cocos 2.x 和 3.x 的全局对象名不一样,2.x 是cc,3.x 变成了_cc或模块化导入方式。游戏启动后,还可以在开发者工具里执行cc.engine或cc.ENGINE_VERSION查看版本号。这一步必须先做,因为不同版本对应不同的资源格式和反编译工具链。
其次是定位资源打包方式。Cocos Creator 项目构建成微信小游戏后,资源通常打在assets文件夹内,代码逻辑在game.js或分包的js文件里。资源可能是明文 JSON 配置、PNG/JPG 图片,也可能是加密或自定义格式的二进制文件。切水果跑酷这个小游戏很有意思,它的纹理和音频用了自定义加密,但配置表是明文的,说明作者在资源保护上只做了部分处理,这样我们逆向的难度就大大降低了。
最后是梳理代码执行链路。JS 代码是解释执行的,就算压缩混淆过,也能还原出大致的模块结构。跑酷游戏的启动入口、场景切换逻辑、核心玩法循环,都会在代码里留下清晰的调用标记。顺着启动入口往下追,整个工程结构就浮出水面了。
1.3 工具准备:这几种武器缺一不可
- 微信开发者工具:用于加载小游戏包,调试运行、查看 console 日志、模拟运行环境都靠它。版本尽量用稳定的 RC 或正式版,新版有时会改底层适配。
- Node.js 环境:部分解密脚本、资源解析工具依赖 Node 运行环境,顺手还能做一些 JSON 格式化、代码简单处理的任务。
- Cocos Creator:逆向还原工程时用来对照验证。最好安装和样本游戏相同或相近的大版本,我这边用的是 2.4.x,对应样本的引擎版本。
- 解密与资源查看工具:包括常用的
unveilr(小游戏资源解密工具)、js-beautify(JS 格式化)、PlistEditor或图片处理工具等。具体组合看目标游戏的加密方式而定。 - 文本编辑器:VS Code 或者 Sublime Text 都行,关键是对大体积 JS 文件的检索能力要强,我主力用 VS Code 配合正则搜索。
提示:逆向分析一定要把握好边界。我这次操作仅限于技术研究,所有结论都不涉及他人源码的直接复制,分析过程中也没有绕过任何实质性授权限制。如果你想用逆向手段获取他人商业项目的核心代码去做二次分发,或者破解付费墙,那是另外一回事,风险自担。
2. 从微信小游戏包提取 Cocos 项目资源
2.1 第一步:获取小游戏包文件
微信小游戏和普通网页不一样,不能直接通过浏览器开发者工具抓取全部资源。常规获取方式是把小游戏添加到微信的“我的小程序/最近使用”列表,然后从本地微信缓存目录里找到对应 hash 命名的.wxapkg包。
在 Windows 上,路径一般是:
C:\Users\你的用户名\Documents\WeChat Files\Applet\你的小程序appid\在 macOS 上对应:
~/Library/Application Support/com.tencent.xinwei/Cache/Applet/你的小程序appid/找到.wxapkg后缀的文件后,先复制出来再做解包,不要在原始目录里乱改。这个文件本质上是一种自定义格式的包,可以先用现成工具(比如wxappUnpacker或unveilr)解析出内部文件列表。切水果跑酷这个样本解包后,我看到第一层就是game.js、game.json、project.config.json,以及assets/目录。
这里有个小细节:微信小游戏可能拆分成主包和分包,分包文件名为分包名.wxapkg或以子目录形式存在。跑酷游戏一般主包放核心玩法资源和引擎代码,分包放关卡配置、音效包、新手引导等额外内容。分包在首次加载时不会全部拉取,但本地缓存里通常已经下载过了,所以解包时把同目录下所有wxapkg文件都处理一遍,别漏。
2.2 第二步:处理加密或自定义格式的资源文件
解包出来不等于完事,assets/目录下很多文件看起来是乱码或者带有一堆填充字符,这就是资源加密的痕迹。微信小游戏本身有一个__APP__加密流程,但具体到游戏资源(图片、音频、序列帧)是否再做一层加密,完全是开发者自己决定的。
切水果跑酷这个样本里,.png文件用十六进制编辑器打开,头部不是89 50 4E 47,而是一串随机字节,这就说明图片被做过 XOR 混淆或者其他简单变换。我写了个 Node.js 脚本,尝试几种常见算法:纯 XOR 固定密钥、基于文件名的哈希密钥、AES-ECB 模式,最后用 PNG 文件头89 50 4E 47 0D 0A 1A 0A对密钥进行校验,一分钟就定位到了算法——它是对整个文件做了单字节 XOR 0x5A 的处理。
解密后的资源就规范多了:纹理贴图、图集 JSON、音频文件都按常规格式排列。如果你想自己判断加密方式,一个实用技巧是:先找一份同引擎发布但未加密的样本做对照,对比两者assets目录资源配置的差异,就能快速定位到哪些文件被动过手脚、加密强度大不大。
2.3 如何快速判断引擎版本和资源版本
解密完资源,我对照文件目录结构确认了引擎版本。Cocos Creator 2.x 构建产物有一系列标志性特征:assets/main/index.js或src/settings.js中会写入projectVersion、engineVersion等字段;settings.js里的moduleIds数组结构是 2.x 特有的模块注册方式。3.x 版本则采用 ESM 模块加载方式,产物里常出现.meta对应的 JSON 配置以及更复杂的settings.json结构。
切水果跑酷这个样本用的是 Cocos Creator 2.4.x,这个版本在微信小游戏生态里相当常见,资源系统是 Asset Bundle 机制,配置表和场景文件都以 JSON 形式存储。确认了版本之后,后面很多解析工作就能直接套用对应版本的官方文档和已知工具链,效率完全不一样。
3. 反推游戏场景结构与核心玩法逻辑
3.1 分析 game.js 的模块化组织方式
切水果跑酷的game.js解包后体积超过 6MB,压缩混淆过。我先用js-beautify做了格式化和变量名美化,虽然变量名还是a、b、c之类的短名称,但类名、字符串常量和函数逻辑基本可以读了。
Cocos Creator 2.4 构建出来的代码有一个明显特征:文件开头通常会有一段引擎启动代码,包括设置物理系统、注册组件、加载首场景等逻辑。其后是用户脚本注册区,通过cc.js._setClassId或者_RF.push来注册组件类。我通过搜索特征字符串,比如cc.Class、_RF.push、cc._RF.push,快速定位到了所有自定义脚本的位置。
在这个样本里,我发现它的用户脚本结构相当清晰,大致分为这几块:
- 入口场景控制脚本(启动、登录、加载场景)
- 游戏主循环逻辑(跑酷生成、水果切割判定、分数结算)
- UI 控制脚本(主界面、结算界面、设置弹窗)
- 工具类(音频管理、存档管理、网络请求封装)
- 资源动态加载管理(图集加载、预制体实例化)
这种划分对应到正常 Cocos 工程里,基本上就是assets/Scripts下的几个子目录:Manager、Game、UI、Tools。从反推的角度来说,看到什么样结构的运行时代码,就能还原出什么样的源码组织。
3.2 还原场景和预制体的关键节点
场景和预制体在 Cocos 中是.fire和.prefab文件,在微信小游戏包中对应assets/main/下的.json文件,内容经过压缩但没有强加密。每个节点对象都有_name、_components、_parent等字段,组件里有__type__引用组件类的 ID。
我写了一段 Python 脚本,把场景 JSON 里所有节点和组件的层级关系拉了出来,转成一棵缩进树。切水果跑酷的场景结构粗略看是这样的:
Scene: GameScene ├── Canvas (cc.Canvas + cc.Widget) │ ├── Camera (cc.Camera) │ ├── GameRoot (GameControl 脚本挂载点) │ │ ├── Player (cc.Sprite + Animator) │ │ ├── RoadSpawner (RoadSpawner 脚本) │ │ ├── FruitSpawner (FruitSpawner 脚本) │ │ └── SliceSystem (SliceSystem 脚本) │ └── UILayer (UI 控件集合) │ ├── ScoreLabel (cc.Label) │ ├── ComboLabel (cc.Label) │ └── MenuPanel (UI 预制体)这种结构一眼就能看出来,游戏逻辑层是把“玩家、道路生成、水果生成、切割判定”四个模块分开管理的,UI 则全部集中在 Canvas 下的 UILayer 节点。这种组织方式在真实项目中非常标准,节点树清晰、职责单一,也方便后续拆分包体做热更新。
3.3 从运行时代码反推核心玩法逻辑
场景结构看明白了,下一步就要看代码里这些脚本到底做了什么。逆向 JS 代码最有效的方法是“追关键字符串”,比如"score"、"gameOver"、"spawnFruit"、"onSlice"这些字面量通常会出现在事件派发和数据存储的位置。
切水果跑酷的逻辑链路很典型:跑酷场景里道路是程序化生成的,每跑一段距离就动态实例化新的路面块并销毁旧的;水果生成器定时在屏幕前方刷出水果模型,玩家通过划切操作触发检测;切割判定本质上是射线检测加刀光轨迹的几何求交。这些方法在混淆代码里都能通过相邻字符串常量一步步还原出来。
我重点看了它的水果切割算法。Cocos 2.4 里没有直接内置的“任意角度切片”API,代码实现是把水果模型拆成上、下两部分预制体,在切割瞬间隐藏原模型、实例化两个半块,给半块加上刚体和速度向量,然后用cc.tween把半块旋转到指定角度。这个方法不复杂,但表现效果很好,说明作者在切片表现上做了不少打磨。
跑酷的碰撞检测也值得单独提一句。按代码里的物理系统配置看,路面的碰撞体用了静态刚体加 Box Collider,水果和角色用的是动态刚体。物理引擎每帧的步进参数、重力向量、速度衰减值都可以从设置代码里直接读出来,这些参数对调玩法的“手感”非常关键。
实操心得:混淆代码虽然变量名都短,但函数内部的结构、字符串常量、调用顺序都还在。我的经验是每还原出一个模块,就立刻在注释里写清楚这个模块对应的是“源码工程里的哪个目录哪个文件”,最后汇总的时候会非常省事,不然隔几天再回来看,短变量名会让你重新陷进迷雾里。
4. 还原 Cocos Creator 工程目录结构
4.1 从产物推断源码目录布局
到了这一步,我手里已经有了场景 JSON、预制体 JSON、脚本代码和资源文件,接下来要做的就是把这些碎片拼回一个可以打开的 Cocos Creator 工程。
正常的 Cocos Creator 2.4 工程结构如下:
assets/ ├── Scenes/ ├── Scripts/ │ ├── Manager/ │ ├── Game/ │ ├── UI/ │ └── Tools/ ├── Prefabs/ ├── Textures/ ├── Atlas/ ├── Audio/ └── resources/我对照运行时代码里出现的资源加载路径、预制体名称、场景名称,将资源文件归位。切水果跑酷项目里有一个明显特征:代码里所有动态加载资源都走cc.resources.load,说明作者把核心动态资源放在了resources目录下,静态引用的资源则分散在各自模块目录。
还原过程不是无脑复制,需要注意几件事。第一,代码里引用文件名和实际文件名有对应关系,必须以代码里的引用路径为准来建立目录。第二,场景 JSON 和预制体 JSON 里记录的资源 UUID,要与还原后的.meta文件里的 UUID 保持一致,否则工程打开后会提示资源丢失。第三,脚本组件的类名和脚本文件名要对应,cc.Class里的name属性要和脚本文件名一致,否则组件关联会失效。
4.2 恢复 .meta 文件与 UUID 映射关系
这一步骤是整个还原过程中最容易卡壳的地方。Cocos Creator 里每一个资源文件都对应一个.meta文件,里面记录了资源的uuid、subMetas、importer类型等关键信息。微信小游戏包里通常不会保留.meta,但资源内部引用都依赖 UUID 来定位。
我是怎么恢复的呢?有两个信息源:一个是场景/预制体 JSON 里每个资源引用都会带__uuid__字段;另一个是settings.js里的uuid到url的映射表。Cocos 2.4 构建时会生成一个resources配置块,把资源路径和压缩后的 UUID 做了映射,我写脚本解析这个映射表,反过来生成每个文件对应的.meta内容。
恢复规则是:图片和音频资源的 UUID 就是映射表里查到的值;.fire场景和.prefab文件的 UUID 可以通过文件中__uuid__引用反向推导;脚本文件的 UUID 需要从代码注册的_RF.push参数里提取。我写了个 Node.js 脚本批量处理,最终 98% 的资源都成功映射了出来,剩下的几个手动根据引用关系补上了。
4.3 搭建可运行工程,让还原结果自己验证
还原工程的终极验证方式是:打开 Cocos Creator,新建同版本 2.4.x 工程,把还原的资源、脚本、场景依次放进去,看编辑器能不能正常打开、场景能不能预览运行。
这一步我踩了大坑。刚开始我原封不动把还原出的脚本放进工程,结果 Cocos Creator 报了一大片脚本编译错误。原因很好理解:我逆向出来的代码是压缩混淆过的,类名、变量名都变了,脚本内部逻辑虽然等价,但外部依赖的类名(比如GameControl)已经变成了没有可读性的名称,而场景 JSON 里组件的__type__引用的却是原始类名。两边对不上,组件自然丢失。
解决思路是:不追求直接跑通原逻辑,而是把还原出的场景、资源、预制体作为静态资产导入,脚本则由我根据逆向理解重新编写简化版,保证场景能加载、节点树能显示、资源能引用到。这个简化版不是原代码翻译,而是“遵循原架构思路但重新实现”的版本。它跑起来不一定和线上版一模一样,但工程结构、资源组织、场景层级已经完全复现,对学习来说已经足够。
注意:如果你只是想要美术资源和场景结构参考,不需要让场景完整运行。只要资源恢复正确、场景 JSON 能导入编辑器,就算大功告成。要让代码逻辑也完全还原,工作量和难度会指数级上升,我这边只做到了逻辑层面的“功能等效”还原。
5. 常见问题与排查技巧实录
5.1 解包和资源解密阶段的高频问题
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
解包后game.js是空文件或乱码 | 微信客户端对小游戏 JS 做了加密或封面(IEF)处理 | 使用支持新版本解密的工具,或从旧版本微信客户端缓存中提取原始包 |
| 图片解密后仍打不开 | 图片可能是自定义格式或图集被打包成二进制 | 搜索文件头的特征字节,根据图集 JSON 中的框架信息手动切割还原 |
| 解包出来的资源数量远少于线上看到的图片 | 有动态加载的远程资源(CDN 或云开发存储) | 先跑一遍游戏,让所有资源加载到本地缓存后再解包 |
| 找不到场景配置文件 | 场景被内联到game.js或打成cc.Bundle | 搜索cc.View或cc.director.loadScene的调用,就能找到场景入口名称和加载路径 |
第一类问题最常见。很多新型小游戏包用了微信的虚拟 DOM 适配和代码保护策略,直接解包会得到一层“外壳代码”。解决方案是对字符流做启发式扫描,找可读的 JS 代码段,截取后拼起来再格式化。我建议备一台安装了多个版本微信客户端的虚拟机,不同版本对同一款游戏的保护策略可能不同,择优使用。
5.2 还原工程时的资源引用和组件丢失问题
逆向还原工程最常见的问题是:资源文件都放到位了,但 Cocos Creator 打开场景后组件丢失、贴图变紫、预制体空白。
贴图变紫通常是.meta文件缺失或者type字段设置错误。Cocos 2.4 里纹理.meta的type要设为sprite-frame或texture,设置错误会导致场景里 Sprite 组件的spriteFrame引用失效。我写了一个工具库,根据后缀名自动补上正确的.meta模板,比如.png对应texture类型,并自动生成子资源spriteFrame。
组件丢失则要关注两点,一是__type__和脚本类名能否对得上,二是脚本组件的执行顺序有没有被场景序列化数据搞乱。还原时优先检查报错日志里提到的脚本文件,再对照场景 JSON 中被引用节点的__type__值,逐一手工修正。
5.3 逆向结论不准确,怎么验证和校准
逆向分析有一个天然问题:你看到的是编译产物,很多信息是隐式的,推导出来的结论不一定百分之百正确。我常用的校准手段有四个。
第一是行为对照。运行原版游戏,观察特定操作对应的表现,再在自己还原的工程里做同样操作,对比逻辑是否一致。切水果切割判定的“刀光方向”,原版里是手势轨迹的切线方向,我从代码里还原出的算法是最近两帧坐标差的方向,两者完全吻合,说明推断正确。
第二是字符串定位。微信小游戏运行时会暴露一些内部错误信息、SDK 返回内容,先打出错误日志,再在格式化代码里搜索对应字符串,可以直接定位到调用现场。
第三是引擎源码对照。Cocos Creator 引擎本身是开源的,如果某段运行时代码看起来“奇葩”,先去引擎源码里找对应实现,很多时候你会发现那不是游戏逻辑,而是引擎内部机制。
第四是线上验证。“在开发者工具里打断点、修改变量、强行触发逻辑”,看游戏反应是否符合预期。这招最直接,但要注意不要对线上服务发起任何恶意请求,只做本地调试。
踩坑记录:我在反推跑酷路线生成算法时,一开始根据路面节点的位置数组猜测用的是“预置路径点”方案,后来对照物理表现发现角色可以任意水平移动,才意识到是“外扩随机生成 + 边界约束”方案。这个翻车经历给我提了个醒:看到数据先不要急着下结论,结合运行时表现多验证几次。
6. 逆向成果在自身项目中怎么用
6.1 借架构:学的是组织方式,不是抄代码
逆向看完一个完整上线项目,最大的收获不是某一段写得很巧妙的代码,而是它的工程组织方式。切水果跑酷这个项目的脚本分层、场景节点规划、资源和逻辑的分离策略,放在自研项目里可以直接作为架构参考。
比如说它的资源加载策略:核心玩法用到的预制体都在首包内直接加载,非核心 UI 和音效用cc.resources.load动态加载,远程运营资源走云存储。这种“首包轻量 + 按需动态加载 + 远程可替换”的三层结构,在微信小游戏这种包体限制严格的环境下非常实用。
再比如说它的存储设计:存档量很小,只存最高分、设置项、玩家 ID,所以直接用wx.setStorageSync同步写,没有引入数据库或复杂的缓存框架。这种克制很值得学习——不是所有项目都要上重量级方案,够用就好。
6.2 借思路:技术选型不只看官方文档
很多开发者纠结“微信小游戏视频播放怎么处理”“物理效果要不要用引擎自带系统”这类问题,官方文档写得比较分散,很难直接给你一个综合结论。逆向真实项目会给你一个非常有价值的参考答案:人家能上线、能稳定跑,说明这套方案的坑基本都被踩完了。
切水果跑酷这个项目里,视频播放方案用的是wx.createVideo结合 DOM 适配层,不是在 Cocos 内部渲染视频帧;物理效果部分用的是引擎内置的 Box2D 物理系统,但做了一层轻量封装;音频播放则是直接用微信小游戏全局的wx.createInnerAudioContext,没有走引擎的 AudioEngine。看到这里你就明白了,在这个体量和玩法下,最省心且不容易出事的组合就是:能用平台原生能力就优先用原生能力,引擎封装只负责游戏逻辑和表现层,不要过度依赖引擎的跨端兼容。
我这里再顺手提一嘴 shader 相关的经验。切水果跑酷的水果汁液飞溅效果,一开始我以为是纯粒子系统,仔细追踪后发现是 Shader 里做了 UV 偏移和透明度渐变,配合少量粒子做补充。水花贴图是一张支持 Alpha 的序列帧,在 Cocos 2.4 中通过cc.Material设置useGamma参数控制渲染效果。如果你在 Blender 里做好的模型贴图放进 Cocos 后出现偏色,多半是模型法线、贴图颜色空间和材质的useGamma设置没对上,用 Cocos 的材质调试面板逐项排除就行。
6.3 借流程:上线项目里的隐藏细节
还有一个容易忽略但非常有用的点:线上项目的代码里会留下很多“工程管理痕迹”,比如版本号常量、更新日志字符串、埋点事件名、AB 测试开关。这些东西在官方 demo 里永远看不到,但恰恰能反映一个团队真实的工作流程。
切水果跑酷的代码里,我找到了版本号从1.0.1到1.3.7的迭代痕迹,以及对应的功能开关变量,比如isNewUserGuideEnable、isDoubleCoinOpen。通过这些变量能看出产品在运营上做过哪些试探、哪些功能上线后又被回收了。对想要做微信小游戏的人来说,这是非常难得的“产品运营逆向案例”。
我自己在完成这次逆向学习后,最大的感触是:逆向不是目的,理解才是目的。通过反推别人家的工程结构,你会被迫去思考非常多的“为什么”——为什么场景要这么拆分、为什么资源要这么组织、为什么物理参数要调成这个值。这些思考,只有在真实项目中反复挖掘才能沉淀下来。如果你也想试试,我建议先挑一个身边最简单的小游戏做练习,按这篇文章的流程走一遍,相信你自己的收获会比看任何教程都大得多。