Js也能写外挂?5 行 代码改掉《植物大战僵尸》的阳光值!

3,035 阅读4分钟

🧠 从前端到内存操作:用 Node.js 写《植物大战僵尸》修改器

谁说前端只能玩页面交互?

本文将带你用 Node.js + memoryjs 操作经典游戏《植物大战僵尸》的内存,修改阳光值、让植物种植无冷却。

不需要 C++,不需要驱动,只要你会 JS,就能一行一行“控制游戏”,轻松写出专属游戏辅助脚本。


🌱 为什么选择 Node.js + memoryjs?

作者作为一名切图仔,不会C++,不会Lua,只会一点易语言。但使用易语言编写脚本时,总感觉蹩脚得很。那么Nodejs能不能写脚本呢? 经过我多方打听,终于找到 memoryjs 这个库,提供了对 Windows 下进程、模块、内存读写的封装:

npm install memoryjs

推荐使用最新封装编译版本:

npm install memoryprocess

搭配 Electron,你甚至可以给它做个 UI。

demo.png


🎮 项目目标

我们以《植物大战僵尸》为例,实现:

  1. 实时修改阳光值
  2. 一键开启/关闭无冷却
  3. Electron实现UI

🏗️ 基础准备:打开游戏进程

首先我们要“定位游戏进程”并获取它的内存句柄(Handle):

const memoryJs = require('memoryjs')

const GameExeName = 'PlantsVsZombies.exe'

function getGameProcessHandler() {
    const process = memoryJs.openProcess(GameExeName)
    if (!process) throw new Error('请先打开游戏')
    return process
}

调用 openProcess 会返回目标进程的 ID、句柄等信息,后续所有内存读写都依赖它。


🧩 结构分析:找到目标地址

游戏的数据(比如阳光值)并不是固定地址,而是通过 模块基地址 + 偏移链 来定位的:

/**
 * 获取模块基地址
 */
function getModuleBaseAddr(process, moduleName = GameExeName) {
    const modules = memoryJs.getModules(process.th32ProcessID)
    const mod = modules.find((m) => m.szModule.toLowerCase() === moduleName)
    return mod?.modBaseAddr
}

/**
 * 计算偏移后的地址
 */
function resolveAddr(process, baseAddr, offsets) {
    let addr = memoryJs.readMemory(process.handle, baseAddr, 'dword')
    for (let i = 0; i < offsets.length - 1; i++) {
        addr = memoryJs.readMemory(process.handle, addr + offsets[i], 'dword')
    }
    return addr + offsets[offsets.length - 1]
}

🌞 示例一:设置阳光值

通过配置管理偏移结构:

/**
 * 关卡阳光的基地址为:[[[Game.exe + 0x2a9f38] + 0x768] + 0x5560]
 */
const GameConfig = {
    set_sunshine: {
        baseOffset: 0x2a9f38, // 例如 Game.exe + 0x2a9f38
        offsets: [0x768, 0x5560], //两次偏移,分别是 0x768,0x5560
        type: 'dword'
    }
}

/**
 * 设置阳光值
 */
function Game_SetSunshine(value) {
    // 先获取进程句柄
    const process = getGameProcessHandler()
    // 获取进程模块基地址
    const base = getModuleBaseAddr(process)
    // 获取阳光的地址
    const addr = resolveAddr(
        process,
        base + GameConfig.set_sunshine.baseOffset,
        GameConfig.set_sunshine.offsets
    )
    // 向这个内存地址写入新的阳光值
    memoryJs.writeMemory(process.handle, addr, value, 'dword')
    // 关闭进程句柄
    memoryJs.closeHandle(process.handle)
}

👆 运行 Game_SetSunshine(9999) 就能瞬间让你财大气粗!


❄️ 示例二:开启无冷却

相较于修改阳光值,无冷却功能需要我们覆盖游戏某段指令(即修改源代码),通过写入汇编字节实现功能开启:

function Game_SetCoolDown(open) {
    const process = getGameProcessHandler()
    const addr = getModuleBaseAddr(process) + 0x87296 // 冷却相关地址
    // addr 便是存储判断冷却的相关代码。
    // 原代码是 0x7E 0x14 --> 修改为 0x7F 0x14
    // 其实就是将判断冷却时间的代码中的 `Jle` 改为了 `Jg`: `小于等于` -> `大于`
    const newBytes = open ? Buffer.from([0x7f, 0x14]) : Buffer.from([0x7e, 0x14])
    memoryJs.writeBuffer(process.handle, addr, newBytes)
    memoryJs.closeHandle(process.handle)
}

🧠 示例三:读取阳光值

你甚至可以把这个值实时展示到 UI(比如 Electron 界面)中:

function Game_GetSunshine() {
    const process = getGameProcessHandler()
    const base = getModuleBaseAddr(process)
    const addr = resolveAddr(
        process,
        base + GameConfig.set_sunshine.baseOffset,
        GameConfig.set_sunshine.offsets
    )
    const value = memoryJs.readMemory(process.handle, addr, 'dword')
    memoryJs.closeHandle(process.handle)
    return value
}

📁 最佳实践:配合Electron提供操作UI界面


⚠️ 注意事项

  • 必须以管理员权限运行 Node 脚本,否则无法访问游戏内存
  • 确保你找到的偏移是当前版本可用的(可配合 Cheat Engine 分析)
  • 这是纯学习研究项目,请勿用于非法用途

🧩 后续拓展方向,欢迎关注

  1. 使用Nodejs调用大漠插件:📊图文识别
  2. 使用Nodejs调用易键鼠的双头盒子dll: ⌨️虚拟输入
  3. 大型网游实战🚀

🏁 总结

通过 Node.js + memoryjs,即使是纯前端背景的开发者也可以轻松进入内存脚本开发的世界。利用现代 JS 工具链,我们可以封装一个安全、可维护的辅助工具。


如果你也想使用Nodejs来编写游戏修改器或者游戏脚本,欢迎一起讨论!


附录

如何运行demo

  1. 克隆仓库
    git clone https://github.com/chinjiaqing/pvz-tools.git
    
  2. 安装依赖,推荐使用yarn
     yarn install
    
  3. 运行
    yarn dev
    
  4. 打包exe
    yarn build:win