当前位置:首页 > 编程开发 > 正文

C++ 游戏辅助绘制系列(二):读内存,把「敌人在哪」变成几个函数调用

约 4816 字 · 预计阅读 13 分钟
一句话总结:本篇解决数据来源——用 OpenProcess 拿句柄、CreateToolhelp32Snapshot 找模块基址,再顺着指针链用 ReadProcessMemory 把玩家坐标、血量、阵营读进自己的进程,上一篇的空画布从此有了可以画的东西。

上一篇我们把玻璃架好了,但画布上只有一个写死的红框。从这篇开始,红框的位置改由数据决定。数据从哪来?答案是:游戏进程的内存里。

老规矩,演示对象是虚构的《星陨 Online》(StarFall.exe),所有偏移都是编的。方法是真的,换个靶场同样适用。

第一步:找到进程,拿到句柄

Windows 不允许你随便读别的进程内存,得先通过 OpenProcess 申请权限。要读内存,PROCESS_VM_READ 是必须的,有些操作还需要 PROCESS_QUERY_INFORMATION。

#include <windows.h>
#include <tlhelp32.h>
#include <cstdio>

uintptr_t GetProcessIdByName(const wchar_t* name) {
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32W pe = {};
    pe.dwSize = sizeof(pe);
    if (Process32FirstW(snap, &pe)) {
        do {
            if (_wcsicmp(pe.szExeFile, name) == 0) {
                CloseHandle(snap);
                return pe.th32ProcessID;
            }
        } while (Process32NextW(snap, &pe));
    }
    CloseHandle(snap);
    return 0;
}

// 用法
DWORD pid = (DWORD)GetProcessIdByName(L"StarFall.exe");
HANDLE hProc = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,
                           FALSE, pid);
if (!hProc) {
    printf("OpenProcess failed: %lu\n", GetLastError());
}

三个常见的翻车点:目标进程不存在(返回 0 要判空);权限不够(用管理员身份运行 VS 和程序);64 位进程句柄混用(自己编译成 x64,别用 x86 去读 x64 的游戏)。

第二步:拿模块基址

游戏里的地址几乎都是「模块基址 + 偏移」的形式,基址每次启动都会变(ASLR 的功劳),所以要动态拿。继续用工具帮助快照,这回拍的是模块:

uintptr_t GetModuleBase(DWORD pid, const wchar_t* modName) {
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
    MODULEENTRY32W me = {};
    me.dwSize = sizeof(me);
    if (Module32FirstW(snap, &me)) {
        do {
            if (_wcsicmp(me.szModule, modName) == 0) {
                CloseHandle(snap);
                return (uintptr_t)me.modBaseAddr;
            }
        } while (Module32NextW(snap, &me));
    }
    CloseHandle(snap);
    return 0;
}

uintptr_t base = GetModuleBase(pid, L"StarFall.exe");

我们的靶场是单模块游戏,直接取主模块基址即可。真实项目里常见的是 client.dll、unityplayer.dll 这类模块,方法一模一样。

第三步:顺着指针链读数据

内存里的数据结构像一棵树,你手里只有树根。以下是本系列虚构的偏移表:

偏移 名称 说明
0x18AC000dwLocalPlayer本地玩家指针(相对基址)
0x191F2C8dwEntityList实体列表基址
0x138m_vecOrigin世界坐标,float[3]
0x100m_iHealth血量
0xF4m_iTeamNum阵营(2 匪 3 警)
0x101m_lifeState0 活着 / 非 0 死亡
0x26A8m_dwBoneMatrix骨骼矩阵基址(第三篇画骨骼用)

这些偏移怎么来的?真实项目里靠逆向工具(Cheat Engine、x64dbg、ReClass)动态分析,那是另一个大话题,系列里不展开。靶场练习同理,先用现成偏移把链路跑通。

图 1:四步链路一图看完,输出是一份 30Hz 刷新的实体快照

读取用 ReadProcessMemory,封装一个模板省得每次写尺寸:

template <typename T>
bool RPM(HANDLE h, uintptr_t addr, T* out) {
    SIZE_T read = 0;
    return ReadProcessMemory(h, (LPCVOID)addr, out,
                             sizeof(T), &read) && read == sizeof(T);
}

uintptr_t localPlayer = 0;
RPM(hProc, base + 0x18AC000, &localPlayer);

struct Vec3 { float x, y, z; };
Vec3  myPos  = {};
int   myTeam = 0;
RPM(hProc, localPlayer + 0x138, &myPos);
RPM(hProc, localPlayer + 0xF4,  &myTeam);

实体遍历的套路是「列表基址 + 索引 × 步长」:

struct Entity {
    Vec3   pos;
    int    hp;
    int    team;
    bool   alive;
};

bool ReadEntity(int index, Entity& e) {
    uintptr_t ent = 0;
    if (!RPM(hProc, base + 0x191F2C8 + index * 0x10, &ent) || !ent)
        return false;
    RPM(hProc, ent + 0x138, &e.pos);
    RPM(hProc, ent + 0x100, &e.hp);
    RPM(hProc, ent + 0xF4,  &e.team);
    uint8_t life = 1;
    RPM(hProc, ent + 0x101, &life);
    e.alive = (life == 0 && e.hp > 0);
    return e.alive;
}

在渲染循环里跑一遍 1 到 32 的索引,把活着的敌人存进数组,画布上画框的数据就齐了。

两个工程上的提醒

读失败的容错。ReadProcessMemory 会失败,原因包括地址刚被释放、跨了不可读页。上面的封装返回 bool,调用处必须处理——很多人图省事忽略返回值,然后被偶发的崩溃折磨一晚上。

读的频率。每帧读几十次 RPM,跨进程调用的开销不小。合理的做法是单独开一个读取线程,比如 30Hz 更新一份快照数据,渲染循环直接用快照。这样读和画解耦,卡顿感会小很多。

下一篇预告

数据有了,但坐标是世界坐标(三维),屏幕是二维的,中间差一步投影。下一篇写 WorldToScreen:读视图矩阵,做向量乘法,把游戏世界里的坐标换算成你屏幕上的像素点——方框画在哪,就靠它说了算。

目录导航
  • 第一步:找到进程,拿到句柄
  • 第二步:拿模块基址
  • 第三步:顺着指针链读数据
  • 两个工程上的提醒
  • 下一篇预告
  • 相关文章:

  • C++ 游戏辅助进阶系列(四):速度预判与边界,给自瞄系列收个尾2026-10-20 00:46:55
  • C++ 游戏辅助进阶系列(三):写视角与骨骼瞄准,把自瞄真正打通2026-10-20 00:46:55
  • C++ 游戏辅助进阶系列(二):目标怎么选,FOV 筛选、权重排序与平滑防抖2026-10-20 00:46:55
  • C++ 游戏辅助进阶系列(一):自瞄的数学地基,从两个坐标算出该转多少度2026-10-20 00:46:55
  • C++ 游戏辅助绘制系列(四):ImGui 菜单与工程收尾,从 demo 到像样的工具2026-10-20 00:46:55
  • C++ 游戏辅助绘制系列(三):WorldToScreen 投影,把三维坐标钉到屏幕上2026-10-20 00:46:55
  • C++ 游戏辅助绘制系列(一):透明窗口与点击穿透,把画布架在游戏上面2026-10-20 00:46:55
  • 渗透测试报告 — Z-BlogPHP 1.7.5 本地部署实例2026-10-20 00:46:55
  • 发表评论

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。