You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Python或Cheat Engine基于THREADSTACK0读取wotblitz内存

问题根因

你的代码无法正常读取内存,核心问题有两个:

  • 错误理解THREADSTACK0指针基址的含义:它不是游戏进程模块的固定偏移,而是对应线程栈的起始地址,进程每次启动、线程重新创建时这个地址都会随机变化,你硬编码计算基址的写法完全无效。
  • 错误使用ReadWriteMemory的多级指针接口:多级指针不能直接把所有偏移求和后加基址计算最终地址,必须从基址开始逐次读取指针值、叠加当前偏移,逐级解引用才能拿到正确的最终地址。
实现步骤

ReadWriteMemory库本身没有封装获取线程栈基址的能力,需要调用Windows原生API拿到游戏主线程的栈起始地址(也就是CE里的THREADSTACK0值),再按照多级指针的规则传入偏移链即可。

注意:请先确认你在CE中找到指针时附加的是游戏主线程,否则拿到的栈基址不匹配会导致读取失败。

修正后的完整代码
from ReadWriteMemory import ReadWriteMemory, ReadWriteMemoryError
from time import sleep as wait
import ctypes
from ctypes import wintypes

# Windows API 常量与结构体定义
TH32CS_SNAPTHREAD = 0x00000004
THREAD_QUERY_INFORMATION = 0x0040
ThreadBasicInformation = 0

class THREADENTRY32(ctypes.Structure):
    _fields_ = [
        ("dwSize", wintypes.DWORD),
        ("cntUsage", wintypes.DWORD),
        ("th32ThreadID", wintypes.DWORD),
        ("th32OwnerProcessID", wintypes.DWORD),
        ("tpBasePri", wintypes.LONG),
        ("tpDeltaPri", wintypes.LONG),
        ("dwFlags", wintypes.DWORD)
    ]

class THREAD_BASIC_INFORMATION(ctypes.Structure):
    _fields_ = [
        ("ExitStatus", wintypes.DWORD),
        ("TebBaseAddress", wintypes.LPVOID),
        ("UniqueProcessId", ctypes.c_void_p),
        ("UniqueThreadId", ctypes.c_void_p),
        ("AffinityMask", wintypes.LPVOID),
        ("Priority", wintypes.DWORD),
        ("BasePriority", wintypes.DWORD)
    ]

# 初始化API函数
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
ntdll = ctypes.WinDLL('ntdll', use_last_error=True)

kernel32.CreateToolhelp32Snapshot.argtypes = [wintypes.DWORD, wintypes.DWORD]
kernel32.CreateToolhelp32Snapshot.restype = wintypes.HANDLE
kernel32.Thread32First.argtypes = [wintypes.HANDLE, ctypes.POINTER(THREADENTRY32)]
kernel32.Thread32First.restype = wintypes.BOOL
kernel32.Thread32Next.argtypes = [wintypes.HANDLE, ctypes.POINTER(THREADENTRY32)]
kernel32.Thread32Next.restype = wintypes.BOOL
kernel32.OpenThread.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
kernel32.OpenThread.restype = wintypes.HANDLE
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
kernel32.CloseHandle.restype = wintypes.BOOL

ntdll.NtQueryInformationThread.argtypes = [
    wintypes.HANDLE, ctypes.c_int, ctypes.POINTER(THREAD_BASIC_INFORMATION),
    wintypes.ULONG, ctypes.POINTER(wintypes.ULONG)
]
ntdll.NtQueryInformationThread.restype = wintypes.LONG

def get_main_thread_stack_base(pid):
    """获取指定PID进程主线程的栈基址(对应CE的THREADSTACK0)"""
    snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)
    if snapshot == wintypes.HANDLE(-1):
        raise ctypes.WinError(ctypes.get_last_error())
    
    te32 = THREADENTRY32()
    te32.dwSize = ctypes.sizeof(THREADENTRY32)
    main_tid = None

    # 找到目标进程的第一个线程(即主线程)
    if kernel32.Thread32First(snapshot, ctypes.byref(te32)):
        while True:
            if te32.th32OwnerProcessID == pid:
                main_tid = te32.th32ThreadID
                break
            if not kernel32.Thread32Next(snapshot, ctypes.byref(te32)):
                break
    kernel32.CloseHandle(snapshot)

    if not main_tid:
        raise RuntimeError("找不到目标进程主线程")

    # 打开线程查询TEB地址,计算栈基址
    h_thread = kernel32.OpenThread(THREAD_QUERY_INFORMATION, False, main_tid)
    if not h_thread:
        raise ctypes.WinError(ctypes.get_last_error())
    
    tbi = THREAD_BASIC_INFORMATION()
    ret_len = wintypes.ULONG()
    status = ntdll.NtQueryInformationThread(
        h_thread, ThreadBasicInformation, ctypes.byref(tbi),
        ctypes.sizeof(tbi), ctypes.byref(ret_len)
    )
    kernel32.CloseHandle(h_thread)

    if status != 0:
        raise RuntimeError(f"查询线程信息失败,状态码:{status}")

    # 根据程序指针宽度自动适配StackBase在NT_TIB结构中的偏移
    ptr_size = ctypes.sizeof(ctypes.c_void_p)
    stack_base_offset = 0x4 if ptr_size == 4 else 0x8
    stack_base = ctypes.c_size_t.from_address(tbi.TebBaseAddress + stack_base_offset).value
    return stack_base

# 初始化进程连接
rwm_svc = ReadWriteMemory()
try:
    process = rwm_svc.get_process_by_name('wotblitz.exe')
    process.open()
except ReadWriteMemoryError:
    print('wotblitz.exe not running!')
    exit(1)

# 指针配置
POINTER_CONFIG = {
    "threadstack_offset": -0x00000EEC,  # THREADSTACK0对应的减偏移
    "offsets": [0x4, 0x8, 0x148, 0x0, 0x30, 0x19c, 0x24]  # 多级偏移链
}

def read_damage_value():
    # 每次读取前重新获取当前线程栈基址,避免线程重建后地址失效
    threadstack0 = get_main_thread_stack_base(process.pid)
    base_ptr_addr = threadstack0 + POINTER_CONFIG["threadstack_offset"]
    # 正确调用多级指针接口:传入第一级指针地址 + 偏移列表
    final_addr = process.get_pointer(base_ptr_addr, POINTER_CONFIG["offsets"])
    # 根据内存值实际类型调整读取方法,读浮点用read_float,读长整型用read_long
    return process.read_int(final_addr)

if __name__ == "__main__":
    while True:
        wait(0.1)
        dmg_val = read_damage_value()
        print(f"\r当前伤害值:{dmg_val}", end="")
注意事项
  • 如果读取值不对,先确认你在CE中用的偏移链和代码里写的完全一致,同时确认读取值的类型(int/float/long等)和内存中存储的类型匹配,ReadWriteMemory提供了read_int/read_float/read_double等对应不同类型的读取方法。
  • 权限不足会导致无法查询线程信息,请用管理员权限运行Python脚本。
  • 如果游戏有内核级反作弊机制,用户态的内存读取可能会被拦截,该方法不适用。

Cheat Engine指针配置参考截图

内容的提问来源于stack exchange,提问作者West Prime

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 03:09:44