一、图色模拟的底层原理与调用链路
图色脚本不读游戏内存,只截取屏幕像素矩阵做特征匹配。核心流程:GDI/DX截图 → 颜色提取 → 区域比对 → 键鼠注入。传奇客户端窗口类名通常为 TForm1 或传奇自定义类,用 Windows API FindWindow 获取窗口句柄后,GetDC 抓前台图;DirectX 模式需用 DX Hook 或 PrintWindow 绕过重绘拦截。
颜色特征提取:传奇界面固定色值多,比如土城安全区地面主色 BGR(78, 94, 52),红药图标角点 BGR(36, 36, 196)。用 GetPixel(hdc, x, y) 单点取色慢,实际用 GetDIBits 批量读位图数据进 BYTE 数组,逐行扫描。特征匹配分两种:单点锚定(一个坐标一个色值)和多点阵列(5-8 个坐标同时比对,容差 ±5 色阶)。多点阵列防误判率远高于单点,比如区分金创药和强效金创药靠图标右上角高光点色值差异。
键鼠注入走 PostMessage/SendMessage 发 WM_LBUTTONDOWN/WM_LBUTTONUP 模拟点击,SendMessage 发 WM_KEYDOWN/WM_KEYUP 模拟按键。直接调 mouse_event 是全局注入,切窗口就断,必须用窗口消息级注入才能后台运行。
二、内存读值的接口与结构体定位
图色上限是肉眼速度,内存读值直接拿角色坐标、血量、背包列表。核心 API:OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) 拿进程句柄 → ReadProcessMemory 读内存。难点是找基址偏移链。
传奇角色属性基址定位路径(以老版 M2 客户端为例):用 CE(Cheat Engine)搜当前血量整数 → 回到游戏掉血再搜变化值 → 锁定血量地址 → 查什么访问了这个地址 → 找到汇编指令 MOV EAX, [ESI+0xDC] → ESI 来自 [EBX+0x08] → EBX 来自全局基址 0x0045A000。最终偏移链:[[[0x0045A000]+0x08]+0xDC] = 当前血量。同理 HPMax 在 +0xE0,MP 在 +0xE4,X坐标在 +0x24(WORD),Y坐标在 +0x26(WORD),地图编号在 +0x30(DWORD)。
背包物品遍历:背包数组基址 [[[0x0045A000]+0x10]+0x04],每个物品槽 0x1C 字节步进,偏移 +0x00 为物品 ID(WORD),+0x04 为数量(WORD),+0x08 为耐久(WORD)。遍历到 ID=0 即为空槽。内存读值比图色快 100 倍以上,且不依赖分辨率、界面缩放、主题皮肤。
三、封包截取与协议解析
图色和内存都受窗口和进程限制,封包直接构造 TCP 数据流发给 GameGate,等于自己写了一个无窗口客户端。工具用 WPE Pro 或自制 DLL 注入 ws2_32.dll 的 send/recv 函数挂钩。
传奇协议头固定 2 字节长度 + 1 字节命令字 + N 字节数据。常用命令字:0x10=登录账号,0x11=登录角色,0x12=行走,0x13=跑步,0x14=攻击,0x20=说话,0x30=捡物,0x31=丢弃,0x32=使用物品。行走包结构:长度(2) + 0x12 + X(WORD) + Y(WORD) + 方向(BYTE)。跑步包:长度(2) + 0x13 + X(WORD) + Y(WORD) + 方向(BYTE)。攻击包:长度(2) + 0x14 + 目标ID(DWORD) + 技能ID(BYTE)。
封包发送用 WSASend 直接往 GameGate 的 TCP 连接写数据,绕过客户端所有校验。但 M2 有包频率限制(每秒最多 10 个移动包),超速直接踢线。封包脚本必须加定时器控制发包间隔 100ms 以上。
四、自动化逻辑构建的分层架构
脚本逻辑分三层:感知层(获取状态)→ 决策层(判断做什么)→ 执行层(发指令)。以自动打怪脚本为例:
感知层:读内存拿当前 HP/MP、X/Y、周围怪物列表(怪物数组基址 [[[0x0045A000]+0x14]+0x00],每个怪物结构 0x40 字节,+0x00=怪物ID,+0x04=X,+0x06=Y,+0x08=当前HP,+0x0C=最大HP)。过滤条件:距离 ≤ 视野范围(默认 9 格)、HP>0、非友方(通过名称颜色判断,名称字符串在 +0x20 偏移)。
决策层:if HP < HPMax * 0.3 → 执行喝药/回城;else if 周围有怪 → 选最近怪物执行攻击;else → 沿路径点移动到下一个挂机点。攻击方式选择:近战怪贴脸发 0x14 攻击包;远程怪保持 2-3 格距离用技能包(0x15 + 技能ID + 目标X + 目标Y)。
执行层:发攻击包后 Sleep(800) 等动画硬直结束再发下一个;喝药走使用物品包 0x32 + 物品槽位(BYTE);回城读回城卷轴坐标发使用包后等 3 秒传送读条。
五、定时循环与状态机设计
脚本主循环用 while(1) 或 SetTimer 定时器,周期 100-200ms 扫一次状态。纯 while(1) 占满单核 CPU,必须 Sleep(100) 让出时间片。状态机用 enum 定义:IDLE(待机)→ WALKING(移动中)→ FIGHTING(战斗中)→ HEALING(回血)→ BACKTOWN(回城)→ DEAD(死亡等待复活)。每个状态对应一个处理函数,主循环根据当前状态调用对应函数,函数返回下一个状态。这样比 if-else 嵌套清晰,且方便加新行为(如捡物、交易)而不破坏整体结构。
六、多开同步与窗口管理
多开传奇客户端每个窗口独立进程,用 EnumWindows 枚举所有窗口标题含"传奇"的 HWND,用 GetWindowThreadProcessId 拿到每个窗口的 pid,分别 OpenProcess 拿句柄。内存读值要每个 pid 单独开句柄;图色截图要每个 HWND 单独 GetDC;封包要每个进程 hook 独立的 ws2_32 连接。多开同步逻辑:主窗口发指令(如"全体回城"),通过共享内存或命名管道广播给所有子脚本进程,子脚本收到后各自执行回城序列。
七、常见脚本故障定位
• 图色点击偏移:窗口分辨率改了但脚本坐标没改,用 GetClientRect 动态获取窗口尺寸再按比例换算坐标。
• 内存读值突然全 0:客户端更新后基址偏移链变了,CE 重新搜一遍,通常只有最后一级偏移会变(如 +0xDC 变 +0xE0)。
• 封包发过去没反应:包长度字段算错(长度不含自身 2 字节)、命令字版本不匹配(不同引擎命令字有差异)、GameGate 校验序列号缺失。
• 脚本跑一会角色不动:被 M2 踢下线(包频率超限或坐标跳变),加发包间隔和路径平滑处理。
• 多开时某个窗口不响应:pid 获取错乱,用 GetWindowText 校验窗口标题后再绑定。
八、脚本与引擎脚本的边界
上述是外部辅助脚本开发。传奇引擎内置 TXT 脚本(NPC 对话/任务/爆率)和 Lua 脚本(996_24 引擎等)是服务端侧逻辑,与外部脚本完全隔离。外部脚本只跟客户端进程交互,服务端脚本只控制 M2 行为。两者不能互相调用——外部脚本无法触发服务端的 [@TakeOn17] 标签,服务端脚本也无法读取外部脚本的内存数据。做自动化逻辑时要分清哪层该放客户端侧(移动、打怪、喝药),哪层该放服务端侧(任务判定、奖励发放、属性加成)。
传奇游戏脚本开发入门:从图色模拟到自动化逻辑构建实操
来源:
作者:
点击:

