标题:使用 PyMem 正确读取多级指针并写入浮点值的完整教程

本文详解 pymem 中多级指针(pointer-to-pointer)的正确解析方法,解决 `could not read memory` 和 `typeerror: cannot be converted to pointer` 等常见错误,通过 `remotepointer` 安全获取最终内存地址并写入 float 值。

在使用 PyMem 进行游戏内存修改时,直接对多级指针链(如 base + offset1 → ptr1 + offset2 → ptr2 + offset3 → target)进行手动 read_long() 嵌套极易出错——尤其当某一级指针为空(0x0)、指向受保护/未分配内存,或地址计算溢出时,就会触发 WinAPIError: 299(ERROR_PARTIAL_COPY),即“只能读取部分内存”,本质是目标地址不可访问。

你原始代码中的核心问题有三处:

  1. 指针解引用逻辑错误:getPtrAdrr() 中循环内对非末尾 offset 使用了 pm.read_long(addr + i),但未校验 addr 是否为有效指针(可能为 0 或非法地址),导致首次 read_long() 就崩溃;
  2. 类型误用:pm.write_float() 要求传入有效的、可写的内存地址(int 类型),而你的 getPtrAdrr() 返回值若因异常中断,可能为 None 或非整数,引发 TypeError: cannot be converted to pointer;
  3. 缺乏错误防护与调试信息:未检查模块基址是否获取成功、未验证每级指针有效性,难以定位哪一级失效。

✅ 正确解法:使用 PyMem 内置的 RemotePointer 类(推荐方式)

RemotePointer 是 PyMem 专为多级指针设计的安全封装,它自动处理地址有效性检查(内部调用 ReadProcessMemory 并捕获 WinAPI 错误),并支持链式解引用,显著提升健壮性。

以下是修复后的完整、可运行示例:

from pymem import Pymem
from pymem.process import module_from_name

# 初始化进程
pm = Pymem("xxx.exe")
game_module = module_from_name(pm.process_handle, "xxx")
if not game_module:
    raise RuntimeError("Failed to find module 'xxx.dll'")
base_address = game_module.lpBaseOfDll

def get_pointer_address(base: int, offsets: list) -> int:
    """
    安全解析多级指针,返回最终目标地址(含最后一级偏移)
    :param base: 模块基址或一级指针地址
    :param offsets: 偏移量列表,如 [0x410, 0xC8, 0x3B0, 0x4C8, 0x158, 0xAF0]
    :return: 最终内存地址(int)
    """
    from pymem.pointer import RemotePointer

    pointer = RemotePointer(pm.process_handle, base)
    for i, offset in enumerate(offsets):
        if i == len(offsets) - 1:  # 最后一级:返回地址+偏移(即目标值位置)
            return pointer.value + offset
        else:  # 中间级:解引用,更新 pointer 为下一级地址
            pointer = RemotePointer(pm.process_handle, pointer.value + offset)
    return pointer.value  # fallback(理论上不会执行到此处)

def unlimited_hunger():
    """持续将饥饿值设为 100.0"""
    target_addr = get_pointer_address(
        base_address + 0x08959C68,
        [0x410, 0xC8, 0x3B0, 0x4C8, 0x158, 0xAF0]
    )
    try:
        pm.write_float(target_addr, 100.0)
        print(f"[✓] Hunger updated at {hex(target_addr)}")
    except Exception as e:
        print(f"[✗] Failed to write hunger: {e}")

# 启动循环(注意:实际 GUI 中应避免阻塞主线程,建议用 threading.Timer 或异步调度)
# while True:
#     unlimited_hunger()
#     time.sleep(0.5)

? 关键注意事项

  • 始终校验模块加载:module_from_name() 可能返回 None,需显式判断;
  • RemotePointer 自动处理异常:若某级指针无效(如 0x0 或无读权限),pointer.value 会抛出 MemoryReadError,你可在 get_pointer_address 外层加 try/except 提供友好提示;
  • GUI 集成避坑:你报错栈显示 command=utility.unlimitedHunger() —— 这是在创建控件时立即执行函数,而非绑定回调!正确写法应为 command=utility.unlimitedHunger(不带括号),否则会启动时就进入死循环;
  • 权限与稳定性:确保以管理员权限运行脚本;部分游戏启用 EAC/BattlEye 等反作弊,直接读写内存可能被封禁,请仅用于单机学习场景;
  • 调试技巧:临时加入 print(hex(pointer.value)) 在每级解引用后输出地址,快速定位哪一级为空或异常。

总结:PyMem 的 RemotePointer 是处理多级指针的黄金标准。抛弃手动 read_long() 嵌套,改用该类不仅能规避 ERROR_PARTIAL_COPY (299) 和类型转换错误,还能让代码更清晰、鲁棒性更强。记住——安全的内存操作 = 显式校验 + 分层解引用 + 异常捕获。