很多人都会问到一个问题是如何在某键按下的时候让CheatEngine产生响应。如果你使用了CheatEngine的自动汇编脚本,就很容易解决这个问题。
首先你必须要保存程序状态(pushad/popad),然后吧你要检查的按键压入栈顶部,调用GetAsyncKeystate。然后通过检查存在ax的返回值查看该键
是否正被按下还是曾经被按下过。第15位为1代表是当前按下的键,第0位为1代表从上次调用该函数到现在的某个时间该键被按下过。
如果该键当前没有没按下就跳出所有的游戏修改代码(例如执行减少血量hp的代码)并且返回给调用者,如果当前被按下就执行你的常规代码。
(设置值,跳过减少血量的hp例程,等。。。)
例子:
origin:
jmp mycode
exit:
mycode:
pushad //由于不知道GetAsyncKeystate改了哪些寄存,我猜是eax,ebx,ecx,我讨厌猜测
pushfd //保存标志寄存器是好习惯
...
push 'X' ;要查看状态的键,对于特殊键需要自己google
call GetAsyncKeyState
//第15位为1代表是当前按下的键
//第0位为1代表从上次调用该函数到现在的某个时间该键被按下过
//查看第0位为1的方法如下:
//and ax,1 //与掩码 0000000000000001做与运算
//cmp ax,1
//jne notpressedsincelasttime
//注意上面的代码只是简单实现,其实有很多好方法但是会让人难以理解。
shr ax,#15 //由于是16位寄存器,右移AX寄存器并将左边填充0,1000000000000000就转换为0000000000000001, 没有高于第15位的位了
cmp ax,1 //如果第15位置1,ax现在的值九尾0或1
jne notpressed
//如果按下了
...
...运行按下该键的响应代码(例如 mov [ecx+24],#100 设置生命值为100)
...
notpressed:
//清理
popfd
popad
originalcodeandothercleanupstuff:
...
jmp exit
教程:自定义扫描:乘8
这个教程会展示如何进行自定义扫描。
由于各种原因大家仍然想进行自定义扫描,下面的自定义扫描脚本会将给定的值乘8,并显示出除8的结果。
地址列表同时会显示正常情况下未除8的结果。
如何使用:
选择自定义值类型,点击新建,写入下面的脚本,点击ok,赋予一个名称,然后扫描你要找的值
代码:
[enable]
{不要改变下面代码中alloc的那些名字,你可以自由增加新的alloc,别忘了在[disable]中dealloc他们}
alloc(checkroutine,2048)
alloc(prologue,2048)
alloc(epilogue,2048)
alloc(fastscanstepsize,4)
alloc(variablesize,4)
alloc(firstscan,4)
alloc(scantext,4) //会获取给定字符串的地址
alloc(scanvalue,8) //will get the value of the input string converted to an 8-byte value
alloc(singlescanvalue,4) //会获取输入的float型值
alloc(doublescanvalue,8) //会获取输入的double型值
alloc(inttostr,1024)
variablesize:
dd 4 //定义每个搜到的结果保存为多少字节
fastscanstepsize:
dd 1 //定义快速搜索时候的步长大小(1=不变)
firstscan:
dd 0 //如果想用上次搜到的值继续搜索,置1
/* routines:
提示: 你可以用任意语言写这些函数,并输出为dll,然后使用Loadlibrary 或者调用exportfunction来使用*/
checkroutine:
/*
edx=指向新值的指针
ecx=指向旧值得指针
*/
mov eax,[edx] //eax获取新值
cmp eax,[scanvalue] //将eax和用户输入值比较
setz al //如果匹配则置al为1,否则置0(忽略eax高比特位)
ret
prologue:
shl [scanvalue],3
//你可以在这里放置要在扫描开始前执行的代码
ret
epilogue:
//你可以在这里放置要在扫描结束后执行的代码
ret
scandisplayroutinetype:
/*
displayroutinetype是一个特殊的全局标志,无需这个地址处的字节决定如何显示值
0=1字节标记
1=2字节标记
2=4字节标记
3=8字节标记
4=float标记
5=double标记
6=数组类型标记
7=ascii字符串标志
8=unicode字符串标志
ff=使用 'scandisplayroutine:' 转换data为字符串
*/
db ff //2=4字节标志
label(inttostr_loop)
label(inttostr_reverseresult)
alloc(tempinttostrbuf,50)
inttostr:
//input:
//eax=value
//edi=字符串存储空间
push ecx
push edx
push edi
push esi
mov esi,tempinttostrbuf
mov ecx,#10
inttostr_loop:
xor edx,edx
div ecx
add dl,'0'
mov [esi],dl
inc esi
cmp eax,0
jne inttostr_loop
///反转结果
dec esi
inttostr_reverseresult:
mov al,[esi]
mov byte [edi],al
inc edi
dec esi
cmp esi,tempinttostrbuf //back at base ?
jae inttostr_reverseresult
mov byte [edi],0
pop esi
pop edi
pop edx
pop ecx
ret
scandisplayroutine:
/*如果 'scandisplayroutinetype:' 设置为 255 这个例程会被调用,转换指定地址的值为ascii字符串
eax=指向字节数组的地址
edx=指向目的字符串的地址 (最多50个字符)
注意: scandisplayroutine 只有16kb
*/
push eax
push edi
mov eax,[eax]
shr eax,3
mov edi,edx
call inttostr
pop edi
pop eax
ret
[disable]
dealloc(checkroutine)
dealloc(prologue,2048)
dealloc(epilogue,2048)
dealloc(fastscanstepsize)
dealloc(variablesize)
dealloc(scantext)
dealloc(scanvalue)
dealloc(singlescanvalue)
dealloc(doublescanvalue)
dealloc(inttostr)
dealloc(tempinttostrbuf)