FPS游戏:实现GDI方框透视

时间:2024-02-21 16:48:38

FPS游戏可以说一直都比较热门,典型的代表有反恐精英,穿越火线,绝地求生等,基本上只要是FPS游戏都会有透视挂的存在,而透视挂还分为很多种类型,常见的有D3D透视,方框透视,还有一些比较高端的显卡透视,在透视实现难度上,方框透视是最复杂的一种,本教程将学习方框透视的实现算法,并编写通用辅助实现透视效果。

方框透视的原理是通过读取游戏中已知坐标数据,并使用一定算法将自己与敌人之间的距离计算出来,结合GDI绘图函数在窗体上直接绘制图形,直到现在这种外挂依然具有极强的生命力,原因就是其比较通用,算法固定并能够应用于大部分的FPS游戏中。

下面的这种绘制方式,是最笨的办法了,但是却最通用,只需要能读取到游戏的坐标,即可完成绘制方框,相比于D3D的注入方式,该方式也更加稳定,水平有限大佬请绕过吧,只能理解到这里了。

寻找游戏坐标数据

教程中使用了【反恐精英:起源】这款经典FPS游戏作为演示对象,在开始编写方框算法之前我们需要获取一些坐标数据,这些数据是用于计算方框的基础,这里需要使用CE工具依次遍历找到 【FOV视场角】【本人坐标数据】【本人鼠标角度】【敌人坐标数据】【玩家数量】【玩家是否死亡】【敌人之间的数组偏移】接下来老司机将带大家把这些基址数据全部都找出来。滴滴滴 ~ 上车!!

找FOV视角: 视场角又称FOV,视场角的大小决定了摄像机的视野范围,简单来说FOV就是屏幕与摄像机之间的夹角,我们可以通过狙击枪的狙击镜来找到游戏的视场角度,当未开镜状态时搜索未知初始化数据(浮点数),开镜后搜索改变的数值(浮点数),依次遍历即可找到该游戏的视场角度,一般的FPS游戏视场角为90度的居多。

1.直接开找,打开CE和游戏,购买一把狙击枪,然后在CE中搜索【未知的初始值】,注意这里要选择浮点数搜索。这把枪是M700当年用这把枪打CF枪枪爆头很刺激,回想当年杀红眼的样子现在还记忆犹新。

2.回到游戏,打开狙击枪的一倍狙击镜,在CE中搜索【变动的数值】,接着打开二倍狙击镜,继续搜索【变动的数值】,最后关闭狙击镜搜索【变动的数值】,该过程要重复10次左右。

3.此时狙击镜处于关闭状态,直接搜索【未变动的数值】,然后拔出你的手枪,搜索【未变动的数值】因为手枪的视野与未开镜状态下的狙击枪是一样的,这样搜索能够尽量排除干扰,从而更精确的筛选到我们所需要的数据。

4.经过了上方的遍历以后,结果已经不多了,我们可以猜测这个角度应该在【0-180度】之间,所以通过【介于两者之间】再次筛选一下结果,之后就可以看到有两个90度的角,而且是绿色的基址,一般情况下开发人员默认会将这个角设置为45,75或90度,这样会方便后期的编程,之间的偏差不会太大。

5.回到游戏,右键手动调整狙击镜视野,发现下方地址栏中的数据也会发生变化,我们双击【242CDD34】这个内存地址,会发现这个基址是由【client.dll+2CDD34】模块加偏移得到的,至此我们已经知道了这款游戏的视场角是90度,而每次开镜【242CDD34】这个地址就会随之变化,从而确定该地址就是FOV。

6.那我们该如何通过代码的方式读取到这个游戏当前的FOV数据呢?这里我通过易语言编写并封装了【透视模块】使用该模块将使透视辅助编写变得简单,后续的内容都会用到这个模块。


找本人坐标数据: 通常情况下(X,Y)坐标的浮动较大不好定位,我们可以找Z坐标因为Z坐标控制人物的高低参数比较好找,首先搜索未知初始值(浮点数)然后跳到箱子上或走向更高的位置搜索增加的数值,回到地面上搜索减少的数值,重复这个过程最后就能找到Z轴的坐标,在游戏中(X,Y,Z)坐标是紧挨着的结构(+0,+4,+8) 找到了Z坐标相应的就可以计算出(X,Y)坐标。

1.打开CE并进入游戏,我们找一个比较平坦的地面,这里所使用的地图是de_dust2,然后使用CE直接搜索【未知的初始值】搜索类型为4字节即可。

2.接着我们跳到第一个高点上,可以跳到箱子上或者是比当前的地面高一些的地方,然后搜索【增加的数值】结果如下。

3.上方搜索完成以后,接着我们跳到第二个高点上,然后继续搜索【增加的数值】搜索结果如下。

4.直接从箱子上跳到地面上,然后搜索【减少的数值】,搜索完成后不要动,直接在地面上搜索【未变动的数值】,这样循环不断的排查。

5.经过不断的尝试与排查,我们已经找到了Z轴的坐标地址是【242CBE4C】,用上方的内存地址每次减去4,即可得到Y与X这两个坐标的内存地址。

直接使用易语言配合透视模块,来读取坐标数据。


找自己鼠标角度: 通常FPS游戏鼠标的准心Y坐标向上抬会减少,鼠标准心向下会增加,不断的遍历(浮点数)就可以搜索到鼠标的准心Y坐标,得到了鼠标的Y坐标之后然后+4就能得到鼠标的X的坐标参数。

1.打开CE进入游戏,将鼠标放置在屏幕的中心位置,直接搜索【未知初始化数据】(浮点数),然后将游戏鼠标向上微抬,回到CE搜索【减少的数值】多次向上抬并搜索减少的数值。

2.接着将鼠标逐步向下微压,回到CE然后搜索【增加的数值】这里要重复十几次,最后不要动鼠标直接搜索【未变动的数值】即可找到以下结果,这里都是基地址选哪一个都可以。

这里我们选择【242CDF9C】这个地址,然后在其基础上+4得到X坐标,通过使用易语言编程获取到这两个参数,代码如下所示:


找单个敌人坐标数据: 首先在开始游戏之前通过控制台暂停对方阵营机器人的走动,使用bot_stop 1命令暂停,暂停后搜索未知初始值,然后使用bot_stop 0命令让机器人走两步后马上暂停,搜索变化的数值,开启机器人走动马上暂停,再次搜索变化的数值,不断尝试直到找到正确的数据,只要找到X轴的坐标,可以通过+4,+8的方式得到(Y,Z)的相对偏移。

1.首先开始一局游戏加入一个机器人(按下+号键添加),然后输入bot_stop 1命令让机器人暂停,在CE中搜索【未知初始数据】。

2.输入 bot_stop 0 让机器人运动两步后马上暂停,然后CE中搜索【变动的数值】这个步骤需要重复多次,最终能够看到有几个非常像坐标的数据,下方的三个标红数据都可以,此处我就直接选择 1CBFFDD8 作为演示对象。

3.将找到的内存地址加入到下方地址栏,然后我们右键选择【找出是什么改写了这个地址】,记下这个偏移数据([esi+15B8]),和基地址([1CBFE820])后期会用到。

4.直接双击这个内存地址,然后可以看到 server_css.dll+3D24E4 模块地址+偏移地址,然后与15B8相加就能得到X轴的坐标数据。

总结:在15B8的基础上每次递增+4既可得到Y轴与Z轴的坐标地址,最终可以用易语言编程获取单个敌人的坐标数据了。


取当前玩家数量: 玩家数量的查找非常简单,大部分的FPS游戏都有人物统计菜单,按下TAB键则可看到,我们可以通过查看人物数量来查找,第一次搜索1,然后按下+号添加1个机器人搜索2,再次添加一个机器人搜索3,不断遍历即可得到玩家数量。

1.打开CE修改器,进入游戏后,按下TAB键即可看到当前只有自己,我们在CE中搜索1即可。

2.按下大键盘下的+号,然后在CE中输入2点击【再次搜索】,以此循环,直至找到绿色的基址为止。

总结:这个取玩家数量太简单了,自己找找就是,上方的绿色地址都可以作为判断依据,这里就直接使用 server_css.dll+3D24B8 这个地址了,易语言读取代码如下所示。


判断敌人是否死亡: 取敌人的当前状态,在CS中我们可以搜索敌人的血量,首先添加1个机器人,然后搜索100,打敌人一枪(不要打头)搜索减少的数值,然后搜索未变动的数值,再次打敌人一枪搜索减少的数值,注意不要把敌人打死了就行,不断的遍历最后就能找到我们想要的敌人的血量,通过血量则可判断该地人似否死亡。

1.进入游戏手动添加一个机器人,此时机器人的血量是100,我们直接搜索精确数值100。

2.用手枪打敌人的脚,不要打头!然后直接搜索【减少的数值】搜索完成以后,直接多次搜索【未变动的数值】重复2-3次即可找到为数不多的几个地址。

上方找到了四个看似与血量相关的地址数据,我们分别将这几个数据改为100,发现当2CC7754C被改为100时其他的地址也跟着变成了100,说明第三个就是人物的血量。

3.接着在第三个地址处右键选择【找出是什么改写了这个地址】,可以看到偏移是【9c】,我们继续搜索 2CC774B0 这个内存地址。

4.此时找到了基地址 server_css.dll+3D24E4 然后加上9C就是当前敌人的血量地址了。

5.我们打死这个敌人,会发现血量变成了1说明这款游戏当人物死亡时,会用1来表示。

总结:知道了这个特性,我们就可以用易语言来判断敌人是否死亡了哈,代码如下:


找敌人之间的数组偏移: 在前面我们已经找到了第一个敌人的数据【server_css.dll+3D24E4】指向的就是第一个敌人的地址,通过与偏移【15B8】相加就能得到X坐标,在此基础上加4就能得到Y坐标,显然该游戏并不会将玩家数据放到偏移中,很有可能每个敌人分别占用一个地址,我们可以通过使用内存遍历工具,找到第二个敌人的地址,然后用第2个敌人的地址减去第1个敌人的地址就能得到敌人与敌人之间的差值。

1.首先进入游戏,添加两个机器人并将机器人暂停bot_stop 1,然后在CE工具的内存地址栏中,添加server_css.dll+3D24E4这个内存基址。

2.接着用易语言编写一个乞丐版的基址遍历器,你也可以通过CE进行结构爬行,网上也有很多基址遍历工具可用,我这里为了方便就直接两行代码搞定,代码如下:

3.游戏中保证只有两个机器人,然后运行这段代码,我们知道第一个地址003D24E4是第一个敌人的坐标数据,由于人物的内存矩阵中数据的排列不会偏差太大,这里我们主要找与6.231969801318e-012偏差不太大的内存来分析。

也可以多加机器人,这样能看的更清楚一些

上方结果中可知:地址003D24F4003D24E4浮点数的后缀相同且偏差不大,可以断定这两个就是两个敌人的基址,此时我们用 003D24F4 减去 003D24E4 等于 10 说明10就是敌人与敌人之间的偏移地址,不同的敌人与敌人之间相隔就是10,最后我们直接使用易语言获取到所有敌人的坐标数据:


绘制屏幕方框与屏幕写字: 绘制外部方框就是调用了GDI绘图函数让其在指定的窗口句柄上绘图,我已经将绘制代码封装,直接调用就好这里就不罗嗦了。

分别调用绘制方框与绘制文字,测试效果如下:


方框透视算法分析

在前面的教程中我们已经手动找到了【FOV视场角】【本人坐标数据】【本人鼠标角度】【敌人坐标数据】【玩家数量】【玩家是否死亡】【敌人之间的数组偏移】这些基址数据,多数情况下类FPS游戏找坐标手法都大同小异,接下来我们将具体分析计算方框的思路,以及实现这些方框绘制算法。

第一象限求角: 假设敌人在第一象限,求鼠标指向与敌人之间的夹角b,可以使用反正切求导。

我们知道自己与敌人的相对(X,Y)距离,可以使用反正切公式求出a角的度数。而我们最终的目的是要求出我们的鼠标指向与敌人之间的夹角b,此时我们可以通过已知的鼠标角度C减去a既可得到b的角度。

第二象限求角: 假设敌人在第二象限,而我们的鼠标依然指向在第一象限,求敌人与X轴之间的夹角度数。

如上图:由于(X,Y)(黑色)是已知条件,我们可以通过X比Y求反正切,即可得到a角的度数,然后与90度相加,即可求出敌人当前坐标位置与X轴之间的夹角度数。

第三四象限: 敌人在第三与第四象限与上图差不多,最终目的就是求敌人的位置与X轴之间的夹角,第三象限应该加180度,第四象限加上270度数。这里就不罗嗦了,很简单的东西。

另外4种特殊情况: 如果敌人在第一象限且与X轴重合,那么敌人与X轴为之间的夹角度数必然为零度,同理如果与Y轴重合的话,那么X轴与敌人之间的夹角度数为90度,以此类推就是这四种特殊情况。

上方的4条象限与特殊情况,如果展开的话一共是8种不同的情况,如下代码就是这八种不同情况,调试下面的这段代码会发现一个缺陷,那就是当我们绕着敌人转圈时,偶尔会出现一个大于180度的角度,这又是两种非常特殊的情况。

特殊情况: 当敌人在第四象限且鼠标角度依然在指向第一象限的情况下,则会出现大于180度的角。

如上图:我们的目标是求鼠标角度与敌人之间的夹角度数,而此时的鼠标指向第一象限,而敌人却在第四象限上,我们用360度减去e角度(e = 敌人坐标与x轴之间的夹角度数),即可得到K角度,用K角度加上M角度,即可得到鼠标与敌人之间的夹角度数,另一种特殊情况敌人与鼠标角度调换位置求角,最终代码如下:

FOV视场角度: 摄像机的作用就是,移动游戏中的场景,并将其投影到二维平面,显示给玩家。

如上图:摄像机与屏幕之间的夹角统称为视场角,游戏中的准星位置到屏幕的边缘是FOV的一半,以屏幕分辨率1024x768为例,当FOV为90度时,则准心与屏幕的垂线构成45度等腰直角三角形,此时的摄像机到屏幕的距离就是一半屏幕长度(1024/2 = 512)的大小。

三维横坐标转屏幕X坐标: 将三维矩阵中的敌人坐标数据,转换为屏幕的X坐标。

如上图:我们需要求出敌人位置的坐标数据,可以使用 (x/y) x (1024/2) 最后还需要加上P的长度,由于窗口的总长度是1024那么我们可以直接除以2得到另一半的长度(512),将敌人位置与另一半长度相加就是敌人投射在屏幕上的X坐标,但是此时我们并不知道(X,Y)的长度,所以需要先求出(X,Y) 如下图所示。

上图中:我们需要求出(X,Y)的距离,此时我们已经知道了M和C的长度,则此时我们可以直接使用勾股定理M的平方 + C的平方 (开方)= Z,得到Z之后,通过 sin a = (x/z) => sin a * z = X 此时我们已经得到的X的长度,接着 cos a =(y/z) => cos a * z = Y 此时我们也得到了Y的长度,最后 (x/y) x 512 + 512 即可得到敌人位置,投射到屏幕上的X坐标。

三维纵坐标转屏幕Y坐标: 三维横坐标搞懂了,这个纵坐标就更简单了,如下图:

上图中:通过tan公式即可推导出d与c的距离,然后将d与c的长度相加,即可得到鼠标指向与敌人位置之间的距离,然后再加上屏幕高度的一半,本游戏屏幕高度为768,所以要加上384即可。

最终屏幕横坐标与纵坐标的转换算法如下所示,最后一点代码不搞了!要搬砖去了!


最后的透视效果如下,此处游戏屏幕必须为1024x768,三维坐标转屏幕坐标算法中已经写死了,其他屏幕尺寸需要自行调整代码中的比值关系与相应数值。

这里在插入一个知识点,就是关于敌人血量的颜色显示问题,要实现以下效果也很简单。

只需要做一个判断即可了。


番外篇(代码的改进)

上方的代码,虽然可以在XP系统上绘制出来,但是如果在win10上会出现卡顿和绘制方框频繁闪烁的问题,这是因为我们的方框直接绘制在了游戏的缓冲区上了,该区域的刷新频率与我们自身频率不符导致的,解决的办法是,自己创建一个窗口,然后将窗口设置为透明状态,并将其附加到游戏上面,在自己的窗口中绘制,即可解决这个问题,透明窗体代码如下。

只需要在上方代码基础上,改进一下,将绘制过程放到程序自身窗体中即可解决问题。

最后关于菜单的绘制,可以使用IMGUI.ec 这个模块来完成,也可以自己绘制菜单。

imgui C语言原版,下载地址: https://github.com/ocornut/imgui 下载好以后,在文件的example有些案例,其中就包括DX9的绘制方法。编译后运行起来看看。

Imgui.ec 模块也可实现窗体的自绘,菜单自会。大致代码如下这段代码,是有问题的,想要在CS起源中使用需要改下即可。

最终可实现动态菜单,这个使用了D3D绘制,所以屏幕上不会出现闪烁的情况。

CSGO 同样可是使用的菜单,该方法通用于各种游戏中,外部D3D就是这个样子,内部的话,不通用。需要劫持函数。

为透视增加血条和距离,需要引入D3D透视模块,只是在上面模块基础上封装的,这种模块网上也很多,很好找到的。

另外,上方演示的CS起源版本是低版本的,高版本的一个我也找了一下游戏的基址数据(CS 起源V87版),上方透视代码不需要改变,只需要替换基地址后即可实现高版本算法透视,将前面功能整合起来,最终形成一个完整辅助。

关注我,下次带大家来找CSGO这款游戏的透视矩阵,并演示如何使用外部D3D绘制完整的方框。


原创:写教程不易,又是画图又是求角,转载请加出处,您添加出处是我创作的动力!