
逆向学习从已上线微信小游戏反推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 是cc3.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-beautifyJS 格式化、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。通过这些变量能看出产品在运营上做过哪些试探、哪些功能上线后又被回收了。对想要做微信小游戏的人来说这是非常难得的“产品运营逆向案例”。我自己在完成这次逆向学习后最大的感触是逆向不是目的理解才是目的。通过反推别人家的工程结构你会被迫去思考非常多的“为什么”——为什么场景要这么拆分、为什么资源要这么组织、为什么物理参数要调成这个值。这些思考只有在真实项目中反复挖掘才能沉淀下来。如果你也想试试我建议先挑一个身边最简单的小游戏做练习按这篇文章的流程走一遍相信你自己的收获会比看任何教程都大得多。