
RPG Maker MV 做出来的游戏在逆向眼里基本属于“半开门”的状态。前阵子接手一个基于 MV 引擎的小体量游戏需求是分析它的核心养成数值和道具掉落逻辑顺带把加密的存档结构理清楚。整个过程走下来最深的感受是MV 系游戏与其说是在“逆向”不如说是在“整理”——整理文件结构、整理加密方式、整理脚本逻辑。真正有技术含量的部分在于你得知道每一步去哪找答案。这篇文章先把完整思路记录下来后面如果还有机会碰到 MV 引擎的其他变种比如 MZ或者套了壳的版本再单独写思路二。先把这一套静态拆解的流程讲透。1. 确认引擎与技术栈为什么先做识别而不是直接翻文件拿到任何一个游戏客户端我做的第一件事永远是“确认引擎”。这不是走流程而是因为引擎决定了后面所有的分析路径。RPG Maker MV 有一个非常鲜明的特征游戏目录里必然有一个www文件夹里面躺着index.html打开之后是一整套基于 Web 技术的游戏循环。如果是 MZ 版本结构几乎一样如果是 VX Ace 或者 XP那就是纯 Ruby 脚本的天下思路完全不同。所以第一步花几分钟做识别能避免后面浪费几个小时走错路。识别方法其实很简单。先看目录里有没有www文件夹有的话十有八九是 MV/MZ再打开www/index.html如果源码里引用了rpg_core.js或者main.js那就基本确定了。MV 版本的index.html会直接加载js/rpg_core.js而 MZ 版本用的是rmmz_core.js。这一步连工具都不用鼠标点开就能确认。另外一个小技巧是看package.jsonMV 早期版本会带上这个文件里面会写name: RPGMV一眼就能认出来。这套引擎能成为逆向圈子的“入门教材”核心原因在于它的技术栈太透明了。MV 的游戏逻辑全部跑在 JavaScript 上而数据层是 JSON本质就是一个跑在浏览器环境里的单页应用。没有编译过程、没有二进制混淆、没有原生代码加固——你看到的 JS 就是引擎原始代码和作者自己写的插件脚本全裸奔。甚至连加密都可有可无很多小游戏作者压根不加密直接 JSON 明文躺在data目录下。确认引擎之后第二个动作是确定版本号。MV 从 1.0 到 1.6.1核心代码有细微差别但大结构不变。查看版本号有个快速方法打开www/js/rpg_core.js在文件顶部注释里通常会写RPG Maker MV version之类的字样。确定版本的主要意义在于你在网上搜到的很多插件、教程和工具都标注了版本兼容范围知道自己用的是哪个版本就能快速过滤有效信息。整个识别过程大概需要五到十分钟但这十分钟决定了后续工作的方向。以 MV 为例一旦确认是“Web JSON JS”的架构你的逆向工具箱就完全变了不再需要 IDA、x64dbg 这一类的原生调试器而是直接切换到 Chrome DevTools、Node.js、Python 脚本上来。2. 文件结构与加密机制找到那把锁再看看锁芯长什么样MV 游戏的文件结构非常标准化就像乐高积木一样每一块放在哪里都固定。核心是www目录底下有这么几个关键子目录目录/文件作用分析价值www/index.html游戏入口确认加载顺序和加密参数www/js/引擎脚本与插件脚本最核心所有逻辑都在这里www/data/游戏数据库JSON角色、技能、道具、地图等配置www/img/图片资源立绘、图标、地图块解密后可直接查看www/audio/音频资源BGM、音效同样是加密重灾区www/save/存档位置运行时生成不随客户端打包但格式固定把目录结构摸完之后下一步就是看加密。MV 提供了一个可选的加密方案用于保护图片和音频资源。如果作者开了这个功能img和audio目录下的文件后缀会变成.rpgmvp和.rpgmvm而不是常规的.png、.ogg。这些文件不是整体加密而是在原始文件头部加了一个固定的头信息再用加密算法处理。判断是否加密方法非常直观打开www/data/System.json看里面hasEncryptedImages和hasEncryptedAudio这两个字段是不是true。如果是说明资源被加密了需要先解开才能提取素材。同时这个文件里还有两个关键字段encryptionKey和signature它们决定了你能否解密。encryptionKey是一个 32 位长度的字符串signature则是用来校验数据完整性的。MV 的加密算法有历史演变。早期版本用的是 AES-256-CBC后来因为兼容性问题和加载性能1.6.0 之后的版本改成了 RC4 流加密。解密原理不复杂游戏运行时rpg_core.js里的Decrypter类会读取encryptionKey截取前 16 位key.slice(2, 34)之类的操作作为实际密钥然后对.rpgmvp文件头部的特定长度数据做解密还原出真实的文件头剩下的数据块再按算法解出来。对于想做素材提取或者资源替换的人来说这个加密算是“纸糊的锁”。网上有不少现成工具比如 GitHub 上的rpgmvp_decrypt、RPGMVDecrypt之类的脚本原理都是把System.json里的 key 抽出来然后用对应的算法把所有加密文件批量还原。真正需要动脑的反而是那些做了二次加工的游戏——有些作者会故意改掉rpg_core.js里的解密逻辑或者在index.html里做文章导致标准工具失效。碰到这种情况就得手动定位Decrypter类看它到底改了什么。3. 资源解密实操从 System.json 到批量还原图片音频这一节直接上实操。我们假设拿到的游戏开了加密目标是把图片和音频全部还原成可查看、可替换的明文文件。整体分三步走。第一步提取密钥和签名。用任何文本编辑器打开www/data/System.json找到encryptionKey和signature两个字段。正常情况下encryptionKey是一串 32 字节的十六进制字符串signature是一段 base64 编码。这两个值先存到一个临时文件里备用。顺便提一句经常有人拿到的游戏是没加密的那这一步可以直接跳过img和audio目录里的文件直接就是原图原声。第二步写解密脚本。下面的脚本思路是通用的无论是 AES 还是 RC4核心逻辑一致读取加密文件头部用密钥解密前 16 字节的头部信息还原出真实文件头然后再把解密后的头部和剩余密文拼接起来写回新文件。下面给一个伪代码级别的示例以早期 AES 版本为例import base64 import struct from Crypto.Cipher import AES def decrypt_rpgmvp(filepath, key_hex): # key 需要从 hex 字符串转成字节 key bytes.fromhex(key_hex[2:]) # 去掉开头可能的 0x 前缀 cipher AES.new(key, AES.MODE_CBC, IVbytes(16)) # IV 通常是全零 with open(filepath, rb) as f: data f.read() # 头部固定有一段明文元信息先解出真实文件头长度 header_size struct.unpack(I, data[0:4])[0] decrypted_header cipher.decrypt(data[4:4header_size]) # 真实文件 解密后的头部 剩余密文 real_content decrypted_header data[4header_size:] output_path filepath.replace(.rpgmvp, .png) # 按实际类型改后缀 with open(output_path, wb) as f: f.write(real_content)如果你搞不清到底是 AES 还是 RC4最快的办法是打开www/js/rpg_core.js搜索Decrypter看createInMainThread函数里面调用的是AESDecrypt还是RC4Decrypt。名字写得很直白一看便知。RC4 版本的解密脚本网上多如牛毛自己写也简单就是一个ARC4类输入 key 循环异或。第三步批量处理。写个小脚本遍历img和audio目录把所有.rpgmvp文件改名还原为.png把.rpgmvm还原为.ogg也可能是.m4a然后跑一遍解密。成功后你会发现图片能直接打开了音频能直接拖进剪辑软件了。到这一步素材层的“壳”已经被脱掉剩下的是更核心的——数据与逻辑层。这里必须提醒一个坑有些作者为了保护资源会在rpg_core.js里把encryptionKey加密存放再用一段自定义函数动态解密出真正的 key。遇到这种情况静态搜索 key 不好使了得改用动态调试。做法是用 Chrome 打开index.html在Decrypter初始化处下断点运行时在内存里把最终的 key 打出来。这部分涉及动态调试属于思路二的范畴这篇文章里点到为止。4. 数据层的“明文宝库”System.json、Actors.json、Items.json 能告诉你什么资源解开之后真正好戏才开始。MV 的数值配置全部在data目录下清一色 JSON 文件。这些文件在未加密状态下就是纯文本漂亮得让人感动。它们的命名有严格规律System.json系统配置、Actors.json角色、Classes.json职业、Skills.json技能、Items.json道具、Weapons.json武器、Armors.json防具、Enemies.json敌人、Troops.json敌群、Map001.json这类以数字命名的文件地图数据。这些 JSON 的字段结构在 MV 引擎内部有一套固定规范网上也有现成的文档可以直接对照。以Actors.json为例每个角色对象长这样{ id: 1, name: 主角, classId: 1, initialLevel: 1, maxLevel: 99, traits: [ {code: 22, dataId: 0, value: 100} ], params: [ [100, 110, 120], ... ] }params数组里存的是各个等级下的基础属性成长值每一行对应一个属性最大HP、最大MP、攻击、防御、魔攻、魔防、敏捷、幸运每列对应一个等级。你想知道某个角色 30 级时的攻击力是多少直接读第 2 列第 3 行就完事了。这套数据模型极其直白几乎不需要任何逆向技巧纯粹是“会读 JSON 就会提取数据”。Items.json更典型它直接暴露了物品的完整定义{ id: 10, name: 回复药水, itypeId: 1, price: 200, tpGain: 10, hpRecovery: 300, effects: [ {code: 11, dataId: 0, value: 1} ] }effects数组里的code对应 MV 的伤害/效果类型表dataId和value分别是目标参数和数值。比如code: 11表示“恢复 HP”value: 300就是恢复量。配合一张效果类型对照表你就能把整个游戏的物品数值全部反推出来。地图文件也很有看头。每个MapXXX.json都包含events事件、data地图块数据等字段。重点提一下events它记录了地图上所有交互点宝箱、NPC、剧情触发点的坐标和条件。比如某个宝箱里放的物品 ID 是多少、打开条件是拿到什么钥匙、事件触发后会不会调用公共事件全在这里面。想快速摸清一个游戏的隐藏内容分布直接扫events里面code: 122物品奖励和code: 121开关控制的类型就够了。数据分析到这一步游戏的核心数值、经济系统、掉落池基本就全在你手上了。对于做数值分析、游戏评测或者二次创作的同行来说到这里信息已经足够。如果需求还涉及到“动态修改内存数值”或者“破解联网校验”那就得往脚本逻辑层继续挖。5. 脚本逻辑定位从 main.js 到插件代码快速锁定关键函数MV 的游戏逻辑可以分成两层引擎层和插件层。引擎层是rpg_core.js、rpg_objects.js、rpg_scenes.js、rpg_managers.js、rpg_sprites.js、rpg_windows.js这一套“rpg_”开头的文件它们是整个游戏的骨架插件层是js/plugins.js注册的独立脚本通常是作者自己写的或者从社区拿来的第三方插件文件放在js/plugins/目录下。绝大多数“定制功能”都在插件层里引擎层很少被魔改。定位关键函数的效率方法是养成“搜字符串”的习惯。MV 游戏里的数据、提示、选项都是以明文形式存在于 JS 文件的字符串常量中。举个例子你想找“战斗结束后金币翻倍”的逻辑可以先在plugins文件夹里搜“金币”搜不到再搜“gold”或者“money”。字符串搜索是最高效的定位手段没有之一。碰到逻辑藏在引擎层的情况也别怕。MV 的事件系统是“事件指令”制地图上的每个 NPC、宝箱、机关背后都是一系列事件指令而这些事件指令最终会调用Game_Interpreter类的方法。Game_Interpreter定义在rpg_objects.js里它内部有一个巨大的switch (command.code)分支结构每一种 code 对应一种指令行为。比如code: 122是“增减物品”命令code: 101是“显示文字”命令。当你需要精确阻断某段逻辑时比如想跳过某个 Boss 战可以搜索Game_Interpreter.prototype然后在对应 code 的 case 分支里做文章。另一个值得关注的类是DataManager在rpg_managers.js里。它负责加载所有 JSON 数据。如果你是做 MOD 的可以在DataManager.loadDatabase这个函数里动态注入自定义数据。比如新增一个道具、修改一个敌人的属性不用去改原文件直接在加载完成后改内存里的$dataItems数组就完事。这种“运行期注入”的方式比改文件更灵活也便于调试和回滚。插件层的分析也不难。MV 的插件文件头部通常有一大段注释用/ *: * plugindesc之类的格式写明插件用途、参数和版本。如果作者没有粗暴地把注释删掉你甚至不用读代码光看头注释就知道这个插件负责什么。对于做逆向分析的人来说插件列表plugins.js就是一张“功能地图”先把地图轮廓画出来再慢慢往里面填细节。6. 常见问题与排查技巧我在这条路上踩过的坑整个流程走下来难免会遇到一些莫名其妙的问题尤其是不同作者会用不同姿势“魔改”引擎。把这段时间踩过的坑整理成速查表再把典型场景展开聊一下能帮后来人省下不少时间。现象可能原因处理方式资源解密后图片花屏/打不开加密算法判断错误把 RC4 当 AES回rpg_core.js查Decrypter类实际调用的算法函数找不到encryptionKeySystem.json 被二次加密或 key 动态生成用 DevTools 下断点运行时从内存取 key改完 JS 后游戏白屏文件编码或语法错误浏览器直接 JS 报错打开浏览器控制台F12看具体报错行存档无法解密存档用了StorageManager自定义加密搜索StorageManager类的加密方法走一遍它的加解密流程地图事件找不到宝箱逻辑事件被公共事件Common Events远程调用在CommonEvents.json里按被调用 ID 索引查插件报错但不知道是哪个插件某个插件不兼容当前 MV 版本检查plugins.js里插件参数是否符合插件头注释的格式要求其中存档解密是大家问得最多的问题。MV 的存档也有“加密”但它用的是浏览器localStorage或Web Storage机制不是传统意义上的文件加密。StorageManager类的逻辑是把存档对象序列化成 JSON然后用LZString压缩再用一个可选的 xor 混淆或 AES 加密写入 localStorage。想解密存档最简单的方法也和运行时相关——打开游戏在 DevTools 的 Console 里调用StorageManager.loadGame相关的接口系统会直接返回对象规避掉手动解密步骤。还有一个高频坑改了rpg_core.js里某段逻辑后游戏直接卡死或逻辑错乱。这通常是因为你改的时候没有遵循 MV 的“原型链覆盖”约定。正确做法是在插件文件里用Game_Interpreter.prototype.xxx function() { ... }这种形式覆盖原方法而不是直接改引擎文件。直接改引擎文件不是不行但会让后续维护变得极其痛苦而且一旦引擎版本升级改动就全废了。我个人的习惯是所有改动一律以插件方式加载哪怕是临时测试代码也放进plugins.js里注册这样出了问题能一键禁用快速定位。7. 这套逆向思路的适用边界与后续扩展方向文章最后聊两句这套思路的适用边界。整套静态分析流程最快的情况下能在一两个小时之内完成从拿到包到提取出完整数据和内容的全部步骤但它并不是万能的。这套思路只适用于“纯正”的 RPG Maker MV 游戏。如果作者做了这些事思路一就会失效加壳或打包成可执行文件有人会用 NW.js 打包虽然里面还是那套 Web 技术但入口变成了.exe需要先从里面有package.nw或类似结构里把www解出来。好在解包难度不大解出来之后继续走本文的流程即可。深度魔改引擎源码把rpg_core.js里的类名、函数名全部改成无意义乱码甚至把data目录的 JSON 改成自定义二进制格式。这种情况下数据层的解析就不能直接套字段名了得先做“符号还原”和“格式逆向”。混合开发部分游戏会把核心战斗逻辑放到原生模块里通过桥接层和页面通信防御性比纯 JS 高不少。如果你遇到的是这些情况思路二就应该以动态调试和 Hook 为主——用 DevTools 断点分析运行时行为用代理拦截资源请求用代码注入的方式在内存里修改数值和逻辑。这篇文章里提到的很多静态找位置技巧在动态调试时依然通用只是验证和修改手段从“改文件”变成了“改内存”。我个人在实际操作中的体会是MV 游戏的逆向是一个“投入产出比”极高的方向。它不需要太多底层知识主要是耐心、细心和一个能快速验证思路的工作流。如果你刚开始尝试建议找一个没加密、没魔改的小游戏练手比如那种 1 小时通关的作者自制游戏先把从识别引擎到提取数值的流程完整走一遍然后再挑战带加密、带自定义插件的复杂案例。熟练之后你会发现RPG Maker MV 这套体系就像一本翻开的书每一页都写着答案你只需要知道从哪页开始读就好。