在针对游戏制作一些辅助工具时,除了修改游戏数据外,很多情况是直接通过call游戏函数来实现对应功能,例如频道喊话、打坐修炼等功能。那么如何做到正确的游戏函数调用呢?我认为应该从参数确定、堆栈平衡、上下文环境保护、函数返回值等几个方面来考虑。
1、参数确定
参数确定一般需考虑参数个数、否有寄存器传值、是否有this指针以及是否其他隐含信息等几个方面。我们现在对这几个方面逐一讲解。
1)、参数个数
方法一:函数参数个数首先可以通过在调用前push了多少个变量到堆栈中简单确认,但是这样确认不一定准确。还需配合分析游戏函数中使用了堆栈中的哪些值。例如代码清单1所示代码,可以看到call MessageBoxA前面有四个push,并且根据经验判断,基本上可以确认MessageBoxA有四个参数:
代码清单1:
text:00411400 push 0 ; uType
text:00411402 push offset Caption ; "HelloWorld"
text:00411407 push offset Caption ; "HelloWorld"
text:0041140C push 0 ; hWnd
text:0041140E call ds:__imp__MessageBoxA@16 ; MessageBoxA(x,x,x,x)
方法二: 直接通过游戏函数中add esp ,XXX或者retn XXX来确认游戏参数,参数个数一般等于XXX / 4。当然,这种判断方法要首先排除寄存器传值。至于寄存器传值情况则在后面会讲到。代码清单2中 add esp, 8与代码清单3 中retn 8都可以分别判断出cdecladd 与stdcalladd有8/4= 2 个参数。
代码清单2:
text:0041152E push 2 ; b
text:00411530 push 1 ; a
text:00411532 call j_?stdcalladd@@YGHHH@Z ; stdcalladd(int,int)
text:00411537 mov [ebp+sum1], eax
text:0041153A push 3 ; b
text:0041153C push 2 ; a
text:0041153E call j_?cdecladd@@YAHHH@Z ; cdecladd(int,int)
text:00411543 add esp, 8
text:00411546 mov [ebp+sum2], eax
text:00411549 mov esi, esp
代码清单3:
text:004113E0 ; int __stdcall stdcalladd(int a, int b)
text:004113E0 ?stdcalladd@@YGHHH@Z proc near ; CODE XREF: stdcalladd(int,int)j
text:004113E0
……// 省略掉中间代码
text:00411407 mov esp, ebp
text:00411409 pop ebp
text:0041140A retn 8
2)、是否有寄存器传值
这个确认一般比较简单,分析被调用游戏函数代码,假如一个寄存器没有被赋值就被使用(存在读取寄存器中值的操作),那么一般可以确认该函数有通过此寄存器传递值。例如代码清单4中,mov [ebp+b], edx 与mov [ebp+a],