内存基址数据

摘录前人们总结的内存数据, 简单地介绍使用方法.

如无特殊说明默认使用游戏版本 1.0.0.1051.

指针表

其中较全的是 这一篇, 离线版本 (2020-08-10 同步).

使用说明

地址用 16 进制数表示, 未加额外前缀, [x] 表示地址 x 处的数据.

游戏以 6A9EC0 为基址, 每向右一层偏移一级, 沿纵线向上到顶为对应的偏移值, 横线右侧为对应数据的最后一级偏移.

数据类型默认为 4 个字节的有/无符号整型 (int / unsigned int), 常见的还有 1 个字节的字符/字节/布尔型(0表示假其他表示真) (char / byte / bool), 以及 4 个字节的浮点型 (float) 和 8 个字节的双精度浮点型 (double).

同时存在 ASCII UNICODE 等编码方式的字符串.

编译器的优化过程中出于字节对齐考虑也可能会使用 4 个字节来存储 bool 型变量.

以阳光数值为例, 查阅可得到 [[[6a9ec0] +768] +5560] 这个表达式, 它的含义为: 读取地址 6a9ec0 的值记为 a, 读取地址 a+768 的值记为 b, 读取地址 b+5560 的值记为 c, c 即为当前阳光数. 其中中间变量 a 和 b 为指针类型, 目标数值 c 为整型.

对于对象序列, “+s下一个” 中的 s 为每个对象的结构体大小. 读取序列中的第 i (0 <= i < 1024) 个元素时需要给最后一级偏移额外加上 s * i. 比如位于序列第 42 个位置的僵尸的属性倒计时: [[[[6a9ec0] +768] +90] +68+150*42].

Cheat Engine

打开游戏进程, 手动添加地址, 选择目标数值类型, 勾选指针类型, 按需求增加或者移除偏移, 修改基址和各级偏移量.

在这个例子中, 读取地址 6a9ec0 得到 001E9E10, 读取地址 001E9E10+768 得到 13E7FBC0, 读取地址 13E7FBC0+5560 (即 13E85120) 得到目标数值 9990. 中间变量均为指针, 结果按 4 字节整型解析.

CE读取阳光

C++ / Win32 API

使用操作系统提供的接口函数 ReadProcessMemory 来读取进程的内存数据, 需要安装 C++ 编译器和 Windows SDK.

可能用到的 Windows API 有:

为了更好的支持中文, 可事先定义 UNICODE _UNICODE 宏, 或者手动指定使用 W 版的函数.

一个简单的例子

#include <iostream>
#include <Windows.h>

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "kernel32.lib")

int main()
{
    HWND hwnd;
    DWORD pid;
    HANDLE handle;

    hwnd = FindWindowA("MainWindow", "Plants vs. Zombies");
    GetWindowThreadProcessId(hwnd, &pid);
    handle = OpenProcess(PROCESS_ALL_ACCESS, false, pid);

    unsigned int pvz_base;
    unsigned int main_object;
    int32_t sun;

    ReadProcessMemory(handle, LPCVOID(0x6a9ec0), &pvz_base, 4, nullptr);
    ReadProcessMemory(handle, LPCVOID(pvz_base + 0x768), &main_object, 4, nullptr);
    ReadProcessMemory(handle, LPCVOID(main_object + 0x5560), &sun, 4, nullptr);

    std::cout << "current sun: " << sun << std::endl;

    CloseHandle(handle);
    return 0;
}

稍微复杂点的例子

#ifndef UNICODE
  #define UNICODE
#endif

#ifndef _UNICODE
  #define _UNICODE
#endif

#include <iostream>
#include <initializer_list>

#include <Windows.h>

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "kernel32.lib")

static_assert(sizeof(uintptr_t) == 4);

HWND hwnd = nullptr;     // 窗口句柄
DWORD pid = 0;           // 进程标识
HANDLE handle = nullptr; // 进程句柄

// 查找游戏窗口打开进程句柄
bool FindGame()
{
    hwnd = FindWindowW(L"MainWindow", L"Plants vs. Zombies");
    if (hwnd == nullptr)
        return false;

    GetWindowThreadProcessId(hwnd, &pid);
    if (pid == 0)
        return false;

    handle = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
    return handle != nullptr;
}

// 关闭游戏进程句柄
void CloseGame()
{
    if (handle != nullptr)
        CloseHandle(handle);
}

// 读取内存的函数模板
template <typename T>
T ReadMemory(std::initializer_list<uintptr_t> addr)
{
    T result = T();
    uintptr_t buffer = 0;
    for (auto it = addr.begin(); it != addr.end(); it++)
    {
        if (it != addr.end() - 1)
        {
            unsigned long read_size = 0;
            int ret = ReadProcessMemory(handle,                       //
            	                        (const void *)(buffer + *it), //
            	                        &buffer,                      //
            	                        sizeof(buffer),               //
            	                        &read_size);                  //
            if (ret == 0 || sizeof(buffer) != read_size)
                return T();
        }
        else
        {
            unsigned long read_size = 0;
            int ret = ReadProcessMemory(handle,                       //
            	                        (const void *)(buffer + *it), //
            	                        &result,                      //
            	                        sizeof(result),               //
            	                        &read_size);                  //
            if (ret == 0 || sizeof(result) != read_size)
                return T();
        }
    }
    return result;
}

int main(int argc, char const *argv[])
{
    std::wcout.imbue(std::locale("chs"));

    if (!FindGame())
        return -1;

    // 读取阳光数值
    auto sun = ReadMemory<int>({0x6a9ec0, 0x768, 0x5560});
    std::wcout << L"当前阳光: " << sun << std::endl;

    // 读取场上植物信息
    auto plants_offset = ReadMemory<unsigned int>({0x6a9ec0, 0x768, 0xac});
    auto plants_count_max = ReadMemory<unsigned int>({0x6a9ec0, 0x768, 0xb0});
    for (size_t i = 0; i < plants_count_max; ++i)
    {
        auto plant_dead = ReadMemory<bool>({plants_offset + 0x141 + 0x14c * i});
        auto plant_squished = ReadMemory<bool>({plants_offset + 0x142 + 0x14c * i});
        auto plant_type = ReadMemory<int>({plants_offset + 0x24 + 0x14c * i});
        auto plant_row = ReadMemory<int>({plants_offset + 0x1c + 0x14c * i});
        auto plant_col = ReadMemory<int>({plants_offset + 0x28 + 0x14c * i});
        if (!plant_dead && !plant_squished)
            std::wcout << L"序号: " << i << L" "
                       << L"类型: " << plant_type << L" "
                       << L"所在行: " << (plant_row + 1) << L" "
                       << L"所在列: " << (plant_col + 1) << L" "
                       << std::endl;
    }
    auto plants_next_pos = ReadMemory<unsigned int>({0x6a9ec0, 0x768, 0xb8});
    auto plants_count = ReadMemory<unsigned int>({0x6a9ec0, 0x768, 0xbc});
    std::wcout << L"下一个未被占用的植物位置: " << plants_next_pos << std::endl;
    std::wcout << L"当前植物总数: " << plants_count << std::endl;

    CloseGame();
    return 0;
}

Python (pyvz)

# coding=utf-8

from pvz import ReadMemory
from pvz.core import seeds_string
from pvz.core import zombies_string

sun = ReadMemory("int", 0x6A9EC0, 0x768, 0x5560)
print("当前阳光数: " + str(sun))

zombies_count_max = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0x94)
zombies_offset = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0x90)
for i in range(zombies_count_max):
    zombie_dead = ReadMemory("bool", zombies_offset + 0xec + i * 0x15c)
    if not zombie_dead:
        zombie_type = ReadMemory("int", zombies_offset + 0x24 + i * 0x15c)
        zombie_row = ReadMemory("int", zombies_offset + 0x1c + i * 0x15c)
        zombie_x = ReadMemory("int", zombies_offset + 0x8 + i * 0x15c)
        zombie_hp = ReadMemory("int", zombies_offset + 0xc8 + i * 0x15c)
        print("%s僵尸 位于 %d 路 横坐标 %d 本体血量 %d"
              % (zombies_string[zombie_type][1], zombie_row + 1, zombie_x, zombie_hp))

plants_count_max = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0xB0)
plants_offset = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0xAC)
for i in range(plants_count_max):
    plant_dead = ReadMemory("bool", plants_offset + 0x141 + i * 0x14c)
    plant_squished = ReadMemory("bool", plants_offset + 0x142 + i * 0x14c)
    if not plant_dead and not plant_squished:
        plant_type = ReadMemory("int", plants_offset + 0x24 + i * 0x14c)
        plant_row = ReadMemory("int", plants_offset + 0x1c + i * 0x14c)
        plant_col = ReadMemory("int", plants_offset + 0x28 + i * 0x14c)
        plant_hp = ReadMemory("int", plants_offset + 0x40 + i * 0x14c)
        plant_hp_max = ReadMemory("int", plants_offset + 0x44 + i * 0x14c)
        print("植物序号 %d 位于 %d 路 %d 列 血量 %d/%d 类型 %s"
              % (i, plant_row + 1, plant_col + 1, plant_hp, plant_hp_max, seeds_string[plant_type][1]))

参考资料

<植物大战僵尸_BT修改器>(原创+源代码)

公布我所找到的所有基址及各种功能实现方法

【探索发现】我找到的基址、反汇编数据及各种功能实现方法

图片之下——PVZ程序内存中的数据地址列表(PC v1.0.0.1051限定)

HAPPY LUNAR NEW YEAR~

一小波基址正在逼近,以及求指正和解惑

新找到的一些内存地址

最新最完整的植物大战僵尸内存地址(非年度版)