一句话总结:本篇补上自瞄的执行环节——用 WriteProcessMemory 把平滑后的视角写回引擎,再读骨骼矩阵拿头部世界坐标,把瞄准点从胸口升级到爆头线,再把四篇代码拼成一个完整的主循环。
前两篇把「算」的部分全做完了:会算角度、会选目标、会平滑。本篇做「执行」:把新视角写回游戏进程,顺便把瞄准点升级。写内存比读内存敏感得多,本篇末尾会先给一版检测面提示,完整的边界讨论留在收尾篇。
写视角:WPM 与它的规矩
写内存用 WriteProcessMemory,权限要求比读高——PROCESS_VM_WRITE | PROCESS_VM_OPERATION,打开进程时记得补上。封装跟 RPM 对称:
template <typename T>
bool WPM(HANDLE h, uintptr_t addr, const T& val) {
SIZE_T written = 0;
return WriteProcessMemory(h, (LPVOID)addr, &val,
sizeof(T), &written) && written == sizeof(T);
}
struct Angles { float pitch, yaw; };
// 视角地址 = engine 模块基址 + 0x6ABC30(靶场虚构值,顺序 pitch、yaw)
uintptr_t engineBase = GetModuleBase(pid, L"engine.dll");
uintptr_t viewAnglesAddr = engineBase + 0x6ABC30;
bool WriteAngles(const Angles& a) {
return WPM(hProc, viewAnglesAddr, a);
}
三个必须交代的事。第一,写之前读一次当前视角,平滑计算依赖当前值,别用上一帧的旧数。第二,写失败的返回值要处理,页面属性变化、句柄失效都会导致失败,静默忽略会把偶发问题变成玄学问题。第三,写视角的频率跟渲染帧率对齐即可,写得太密没有收益,反而放大异常。
骨骼瞄准:从胸口升级到爆头线
前两篇瞄准点用的是「胸口」= 脚底 z 加 48,这是个近似。要真正打头部,得读骨骼矩阵(bone matrix)。它是一组 3×4 变换矩阵的数组,每个矩阵描述一根骨骼的世界位置和朝向。
靶场的虚构布局:m_dwBoneMatrix = 0x26A8 指向矩阵数组,每根骨骼占 0x40 字节,头部骨骼索引为 6。矩阵内位置分量的排布是每行前三个 float:
struct BoneMatrix { // 单块 0x40 字节,只关心前 12 个 float
float m[3][4];
uint8_t padding[0x40 - 0x30];
};
Vec3 ReadBone(uint8_t boneIndex) {
uintptr_t boneArrayAddr = 0;
RPM(hProc, localPlayer + 0x26A8, &boneArrayAddr);
if (!boneArrayAddr) return { 0, 0, 0 };
BoneMatrix bm = {};
RPM(hProc, boneArrayAddr + boneIndex * 0x40, &bm);
return { bm.m[0][3], bm.m[1][3], bm.m[2][3] }; // 位置在每行第 4 个分量
}
靶场的头部索引是 6。真实游戏里骨骼索引各不相同,常见做法是把 0 到 20 全读一遍画出来看哪个是头——这个调试技巧在绘制系列的基础上加几行就能实现。
顺手把瞄准函数升级:原来 CalcAngle(eye, chest) 的第二个参数换成 ReadBone(6),加个容错(骨骼读失败时回退胸口),爆头线就有了。
主循环:把四篇拼起来
到此为止所有零件齐了,主循环只剩组装:
void AimLoop() {
Matrix4x4 vm;
RPM(hProc, base + 0x1A2C000, &vm); // 三篇的视图矩阵
Angles cur = {};
RPM(hProc, viewAnglesAddr, &cur); // 先读当前视角
// 选目标(二篇):快照遍历 + FOV + 评分
int target = SelectTarget(cur, vm);
if (target < 0) return;
Vec3 aimPoint = ReadBone(6); // 三篇:头部骨骼
if (aimPoint.z == 0) aimPoint = GetChest(target); // 回退胸口
Angles want = {};
if (!CalcAngle(GetEyePos(), aimPoint, want)) return; // 一篇:算角度
ApplySmoothing(cur.yaw, want.yaw, g_smooth); // 二篇:平滑
ApplySmoothing(cur.pitch, want.pitch, g_smooth);
WriteAngles(cur); // 本篇:写回
}
在渲染循环里以每帧一次的频率调用 AimLoop,按菜单开关(ImGui 的 g_boxESP 同款做法加个 g_aimBot)控制启停。到这里,系列的四个功能模块就完整接上了。
图 1:主循环五步,任何一步失败都要安全退出而不是带病继续
写内存的检测面,先打预防针
读内存是「拿」,写内存是「改」,检测强度完全不同。写操作会触碰引擎的完整性校验——视角这种每帧都在变的值相对安全,但写保护页、写代码段立刻触发崩溃或上报。另外 WriteProcessMemory 的调用记录在反作弊的遥测里是高权重信号。本篇只写到「能用」,收尾篇专门讲边界:哪些值碰不得、写频率怎么设计、为什么这些讨论只在靶场有意义。
下一篇预告
收尾篇处理两个提升项:目标在移动怎么办(读速度向量做提前量预判)和网络延迟的补偿思路,然后把检测面、合规边界一次说透,给整个系列收尾。

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