几何基础,多种矩阵的学习,世界坐标到屏幕坐标的两种转换方法,三种绘制方框的原理,hookd3d,hookopengl,骨骼透视,主播的秘密,FPS各种BT功能的原理 和检测对抗原理,UE4引擎,U3D

2023-11-07

为了方便大家可以系统性的学习 和了解FPS游戏相关知识,

导致本帖包含的内容比较繁多.如果没有耐心全部看完的话,也可以直接跳到自己需要的知识点进行学习

下面介绍下

本帖主要内容包含:

几何基础,多种矩阵的学习,世界坐标到屏幕坐标的两种转换方法,三种绘制方框的原理,hookd3d,hookopengl,骨骼透视,主播的秘密,FPS各种BT功能的原理 和检测对抗原理,UE4引擎,U3D引擎的逆向 和实战 ,

游戏安全的建议策略方针 , 反外挂的思路和检测对抗 等等.

同时公众号 任鸟飞逆向后续会给大家专门开设一个FPS安全的版块,进行检测对抗的讨论.

学习这套课程的基础包含少量的汇编知识和编程知识,一定的数学知识和内存知识.

基础建立在 任鸟飞逆向 前100课的前提下即毫无压力的学习.(公众号有免费抽取赠送基础课的名额)

当然我们要从最简单的概念开始学习,请勿急躁

这个课题本着最简单易懂,从本质完全解析的态度,所以有任何细节不懂,哪怕是三角函数,都可以找我探讨

好,我们正式开始

          向量

可能大家问为什么要学习向量, 原因是向量是矩阵的元素,而矩阵是帮助我们快速计算的朋友

所以就算不能完全掌握,了解一下是必要的.

指具有大小和方向的量.

一维向量,例如 1     对应的就是从0到1的向量,大小是1,方向X正轴方向

我们说向量只有大小和方向, 不存在其他属性,所以  一维向量 1 也可以表示 从1到2 从-1到0

向量可以进行算数运算的.例如 1+1 =2   2个大小是1,方向X正轴方向的向量

相加等于1个大小是2,方向X正轴方向的向量

1*3 = 3  给向量放大3倍

二维向量,例如  2,3 书写格式为[2,3]

对应的是 从原点0,0 到 坐标 2,3 的向量, 大小就需要计算了

根据三角函数,大小等于   sqrt(2*2+3*3) ,同样这也是计算 二维空间2点的距离公式

(三角函数:直角三角形,斜边的平方 =  底边平方+高平方 , 知道任意2边可以计算出另外一个边的长度)

距离 = sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));

方向如图所示,我们下面再讲

向量只有大小和方向,同样二维向量 [2,3] 也可以表示 从3,0到5,3 ,可以任意位置开始

二维向量也可以进行算数运算.

例如 [2,3]+[2,1] = [4,4]

向量的乘法 [2,3]*[3,3] = 6+9= 15  向量的内积

向量的减法可以把空间2点的绝对坐标转化为相对坐标

[X1,Y1] - [X2,Y2]= [X1-X2, Y1 - Y2],相当于把向量移动到原点

三角函数角度的问题

在游戏图像计算中角度是必不可少的部分

例如 我们知道了,如下三角形 高为3  底边为2

那么tanA = 3/2   我们想求A的角度怎么办呢?    C++给我们提供API函数

A = atan(3,2);  这样就计算出来   A的角度了

不过atan()的返回值是弧度,我们如果想转为真正的角度  还是需要转换的

什么是弧度呢?你可以简单的理解为  正常角度如果是0-180的话  弧度就是 0- π

那么 atan(3,2) *180 / π   就把弧度转换成角度了

最终

A = atan(3,2)*180 / π;

另外一种情况,

知道了角度,想求边长

例如一个向量[?,5] 角度是东偏北 60度

我们怎么计算向量值呢?

很简单,

tan 60 = 5/底边

底边 = 5/ tan60,当然这里的角度参数也是弧度  ,如果你的是真实角度,我们又要把角度转换成弧度

最终

底边 = 5 /  tan (60*π/180) ;

其他的 sin   cos  也是同理,我们不是学习数学,所以暂时了解即可,后面用到再说.

三维向量  

例如 2,1,3   格式[2,1,3]

向量写成行 或则列都可以

行向量   [2,1,3]

列向量

[ 2

1

3 ]

三维向量对应的是三维空间 2,1,3对应的是x,y,z

(注: 三维坐标系,很多书本是Y 为高度轴,切记X,Y,Z只是个符号,你可以起名叫a b  c 也没问题

调转一下坐标系X,就变成了Y ,所以没有区别,不要死记名字,按照自己习惯来)

[2,1,3]就是从原点到坐标2,1,3的向量

大小计算就更加复杂一点了

先看懂下图的辅助线

根据三角函数,向量的大小等于   sqrt(1*1+2*2+3*3) ,同样这也是计算 三维空间2点的距离公式

距离 = sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2));

而方向不再单纯是一个角度了,他包含水平角度 和 高低角度,这个我们后面再讲

向量的减法可以把三维空间2点的绝对坐标转化为相对坐标

[X1,Y1,Z1] - [X2,Y2,Z2]= [X1-X2, Y1 - Y2,Z1-Z2],相当于把向量移动到原点

同样三维向量也可以进行 加法  乘法等运算

例如[x1,y1,z1] * [1,2,3]  = x1+y1*2+z1*3   向量的内积

到这里是不是对几何和线性代数的基础知识不再陌生了,其实就这点东西,很简单.

          矩阵

为什么要学习矩阵,对于我们研究的课题来说,就是为了方便计算以及精准计算的,当然你可以不用.

多个向量组合成一个矩阵

矩阵可以看做一个特殊的向量,而向量也可以看做一个特殊的矩阵。

只有一行的矩阵   为行向量  行矩阵

只有一列的矩阵   为列向量   列矩阵

格式为  行*列

例如 3*3 矩阵:

1   2  3

5   7  1

2   2  1

例如 3*4 矩阵

1   2  3   5

5   7  1   1

2   2  1   2

同形矩阵  可以相加减(毕竟如果不是同型的话,没有办法对应相加减  这很好理解)

稍微有点难度的是矩阵相乘除

那么大家要注意的是:

1.矩阵是 多个向量的组合,矩阵的乘除就是 向量的乘除,而不是单独元素的乘除

2.两个矩阵相乘,用第一个矩阵的行 乘 第二个矩阵的列的方式 计算

由于使用方法的区别    A*B  !=   B*A   而且 A* 不同的矩阵  结果可能相同

3.计算结果的存放

例如 2个2*2 矩阵相乘

第一行*第一列 放到  第一行第一列

第一行*第二列  放到  第一行第二列

第二行*第一列  放到  第二行第一列

第二行*第二列  放到  第二行第二列

a1   a2           乘       b1    b2         =    a1*b1 + a2*b3            a1*b2 + a2*b4

a3   a4                     b3     b4               a3*b1 +a4*b3             a3*b2 + a4*b4

m*n 矩阵  和 i*j 矩阵  由于是行 *列  所以

m*n 矩阵一行的元素  要和  i*j 矩阵一列的元素   必须相同

也就是  n  == i

主要满足这个条件就可以相乘  否则不可以

矩阵特性

矩阵对于我们来说就是为了方便计算而生,并不是无可取代

举个例子

只有对角线为1  其他都是0的矩阵    单位矩阵

1   0   0     0

0    1   0    0

0    0   1    0

0    0   0    1

任何矩阵乘以  单位矩阵都为原来矩阵 把 1换成2   就是  放大2倍

比你一个元素一个元素的*2方便很多吧?

矩阵取一列

1   0   0     X          乘     0      =     X       取X  Y   Z  向量

0    1   0    Y                   0             Y

0    0   1    Z                   0             Z

0    0   0    1                   1             1

单独放大某个元素

X   0   0     0          乘     0      =    0         Z 扩大10倍

0    Y   0    0                   0            0

0    0   Z    0                   10         10Z

0    0   0    1                   1            1

矩阵的乘法可以实现很多的功能

看起来是不是很方便,很强大?!

          不借助矩阵把游戏坐标转换成屏幕坐标

无论是在窗口上绘制窗体,还是画各种方框,最核心的功能就是在于如何把游戏坐标也就是世界坐标转换成屏幕坐标.

这里我们先不借助于强大好用的矩阵,单纯用几何算法转换一下坐标

图看起来有点乱,我们慢慢来

这是游戏 上方俯视的平面图:

1.水平可视角度 一般为90度, 也就是FOV 或则第一人称视角

但是这个值只是约值,可能不精准也是导致后面不精准的原因之一

2.我们准星一直在屏幕正中心的,所以向前做一个垂线,左右各45度

3.我们把三角形补全,等腰三角形的斜边就是我们的可视范围,任何游戏中的物品 敌人和我们的连线只有和这个斜边有交单才会显示到我们的屏幕中

如图中敌人和我们连线 焦点就是红色圆圈

4.角度A 可以根据具体朝向值简单分析出来,后面数据分析的时候再说

5.红色圆圈 在 AB 这条线上的位置

就是敌人在我们屏幕X坐标的等比例位置

所以这样就可以计算出来 屏幕坐标X了

tanA =  X差/ (AB/2);

那么  X差 = tanA*(AB/2);

X差/(AB/2) =  屏幕坐标差/ (分辨率_宽度/2)

继续替换

tanA = 屏幕坐标差/ (分辨率_宽度/2)

角度还要转换一下成弧度

最终

屏幕坐标差 =  tan(A*π/180)  *(分辨率_宽度/2);

屏幕坐标 =  屏幕坐标差 + 分辨率_宽度/2;

int 水平差 = (int)(tan(水平角度差 * 3.1416 / 180) * ((m_分辨率宽) / 2));

屏幕坐标.x = (float)(m_分辨率宽 / 2 + 水平差);

屏幕坐标.y 也是同样的计算方法,不过屏幕宽高是不相同的,所以可视角也是有区别的

屏幕分辨率_高/屏幕分辨率_宽 = 高低可视角度 / 水平可视角度

int 高度差 = (int)(tan(高低角度差 * 3.1416 / 180) * ((m_分辨率宽) / 2));// 这里也是m_分辨率宽

因为可视角度不是45了,而是分辨率计算出来的角度

屏幕坐标.y = (float)(m_分辨率高 / 2 + 高度差);

最终代码如下:

bool 绘制::世界坐标转屏幕坐标_非矩阵(坐标结构_2& 屏幕坐标, FLOAT  水平角度差, FLOAT 高低角度差){               取窗口信息();    FLOAT 高低可视角度 = (FLOAT)((double)atan2(m_分辨率高, m_分辨率宽)*180/3.1415);    if (fabs(水平角度差) > 45 || fabs(高低角度差) > 高低可视角度)    {        return false;// 不在屏幕范围内    }
    int 水平差 = (int)(tan(水平角度差 * 3.1416 / 180) * ((m_分辨率宽) / 2));     屏幕坐标.x = (float)(m_分辨率宽 / 2 + 水平差);        int 高度差 = (int)(tan(高低角度差 * 3.1416 / 180) * ((m_分辨率宽) / 2));    屏幕坐标.y = (float)(m_分辨率高 / 2 + 高度差);
    return true;}

但是我们发现这样计算出来的画框 是不精准的

主要2个原因一个是角度是约值,一个是计算过程中数值的溢出,尽量使用double 可以减少

也可以通过微调度数等等的方式 把他修正比较准确

          数据部分

本文章中均以单机游戏为例,每一种功能仅提供给网络安全工作者反外挂建议和安全对抗方法.请勿用作非法用途

另外提示对于此类游戏安全和反外挂研究,单机和网络游戏的原理毫无区别,区别仅仅在于个别数据网络验证部分,如果想研讨网络游的安全防护,可观看视频版,以及公众号任鸟飞逆向探讨.

先整理cs1.6数据如下:

(属于基础范畴,任鸟飞逆向前100课即可轻松搞定这里不赘述)

矩阵地址  hl.exe+1820100//这个暂时先不要管,下文会有详细讲解的地方

高低朝向值 hl.exe+19E10C4 //从低到高  89  到  -89

水平朝向值 hl.exe+19E10C8 //  逆时针 从 0 到 360

朝向值找到以后我们发现水平转一圈  是0-360的变化规律

其中朝向算法需要我们详细分析一下

方法很简单,

把朝向写入0  然后W走路 看看坐标的变化规律发现是X增加其他不变,那么0对应X正轴

把朝向写入90  然后W走路 看看坐标的变化规律发现是Y增加其他不变,那么0对应Y正轴

把朝向写入180  然后W走路 看看坐标的变化规律发现是X减少其他不变,那么0对应X负轴

把朝向写入270  然后W走路 看看坐标的变化规律发现是Y减少其他不变,那么0对应Y负轴

最终得到结果

也就是我们不同朝向的值

人物X坐标:hl.exe+195fe58

人物Y坐标:hl.exe+195fe5C

人物Z坐标:hl.exe+195fe60

周围数组

数组最大数量1F  以下n 通用  0为自己

hl.exe+1B5A5C4+24C*n+0       等于0  数组结束

hl.exe+1B5A5C4+24C*n+190  DWORD  ==0  跳过 有可能数组不是顺序存放

对象X坐标 hl.exe+1B5A5C4+24C*n+18C  

对象Y坐标 hl.exe+1B5A5C4+24C*n+18C

对象Z坐标 hl.exe+1B5A5C4+24C*n+190

1为土匪   2为警察 hl.exe+62565C+n*68+4E    

血量 hl.exe+62565C+n*68+68

死亡标志位 hl.exe+62565C+n*68+60

得到的结果 就可以提供给我们封装数据所用了,这已经足够了

          FPS类型的游戏安全性 和 反外挂

说到这,

我们来聊聊为什么FPS类型的游戏安全性及不高 和  反外挂

主要的2个原因

第一设计简单,数据少,通过上面的需要数据就已经知道了,真的很少

第二个原因是特性导致,透视和自瞄等功能都是服务器无法验证的本地操作

所以加大了反外挂的难度.

那么其实针对于FPS的外挂特征,反外挂可以做的事情也是不少的

第一,加大对周围数据的保护,尤其获取范围,不要在极大范围就像玩家投递全地图数据

第二,对hookd3d的检测应该是比较容易的

第三,对绘制函数的检测,当然如果是窗口的覆盖窗口那是存在一定检测难度的

第四,自瞄准星的数据写入检测

第五,鼠标准星移动轨迹的检测

第六,不定时截图上传

等等.

这些我们后面再详细探讨,现在还没有 了解 所有实现过程, 所以 无法透彻的谈  反外挂

          数据封装

按照正常的数据封装方法,

封装代码如下,因为这里我都使用了中文命名

相信大家都可以看懂了,如果有什么不懂可以 ,可以找我(任鸟飞)探讨

struct 坐标结构_3{  float x, y, z;};
struct 朝向结构_2{    float 水平朝向;    float 高低朝向;};
struct 对象结构{    float X_j;    float Y_j;    float Z_j;    float X_H;    float Y_H;    float Z_H;    int Hp;    BYTE 死亡标志位;    BYTE 阵营;    朝向结构_2 角度_j;    朝向结构_2 角度_H;    朝向结构_2 角度差_j;    朝向结构_2 角度差_H;};
class 周围对象{public:    对象结构  对象列表[0x100];    DWORD  对象数量;public:    void 刷新周围数据_Cs();private:    void 计算朝向_Cs(坐标结构_3 目标, 朝向结构_2& 角度, 朝向结构_2& 角度差);};

DWORD Cs_周围基地址 = (DWORD)GetModuleHandleA("hl.exe") + 0x1B5A5C4;DWORD Cs_周围基地址2 = (DWORD)GetModuleHandleA("hl.exe") + 0x62565C;void 周围对象::刷新周围数据_Cs(){  对象数量 = 0;  for (int i = 1; i < 0x20; i++)//  第一个位置空出来  {    if (*(DWORD*)(Cs_周围基地址 + 0x24C * i + 0) == 0)// 直接结束    {      break;    }    if (*(DWORD*)(Cs_周围基地址 + 0x24C * i + 0x190) == 0)// 碰到空坐标对象 跳过    {      continue;    }           // 哪里不懂可以加我2217777779 探讨      对象列表[对象数量].X_j = *(FLOAT*)(Cs_周围基地址 + 0x24C * i + 0x188);    对象列表[对象数量].Y_j = *(FLOAT*)(Cs_周围基地址 + 0x24C * i + 0x18C);    对象列表[对象数量].Z_j = *(FLOAT*)(Cs_周围基地址 + 0x24C * i + 0x190) - 40;    对象列表[对象数量].X_H = *(FLOAT*)(Cs_周围基地址 + 0x24C * i + 0x188);    对象列表[对象数量].Y_H = *(FLOAT*)(Cs_周围基地址 + 0x24C * i + 0x18C);    对象列表[对象数量].Z_H = *(FLOAT*)(Cs_周围基地址 + 0x24C * i + 0x190) + 23;    对象列表[对象数量].阵营 = *(BYTE*)(Cs_周围基地址2 + 0x68 * i + 0x4E);    对象列表[对象数量].Hp = *(DWORD*)(Cs_周围基地址2 + 0x68 * i + 0x68);    对象列表[对象数量].死亡标志位 = (BYTE)*(DWORD*)(Cs_周围基地址2 + 0x68 * i + 0x60);  
    坐标结构_3 目标;    朝向结构_2 角度;    朝向结构_2 角度差;    目标.x = 对象列表[对象数量].X_j;    目标.y = 对象列表[对象数量].Y_j;    目标.z = 对象列表[对象数量].Z_j;    计算朝向_Cs(目标, 角度, 角度差);    对象列表[对象数量].角度_j = 角度;    对象列表[对象数量].角度差_j = 角度差;
    目标.x = 对象列表[对象数量].X_H;    目标.y = 对象列表[对象数量].Y_H;    目标.z = 对象列表[对象数量].Z_H;    计算朝向_Cs(目标, 角度, 角度差);    对象列表[对象数量].角度_H = 角度;    对象列表[对象数量].角度差_H = 角度差;        对象数量 += 1;  }}

朝向值  和角度差的计算过程,根据上面已经得到的朝向数据

我们编写如下代码,为了让理解更简单  这里我分成了4个象限来讲解

如果还是不能完全理解的话,建议翻看我们之前的  关于朝向的课程,当然朝向 有很多种 这里属于最简单的一种

大家可能问算出来的角度差是干什么用的,还记得上篇文章,不用矩阵转换屏幕坐标吗,里面我们是需要用到这个角度差的

void 周围对象::计算朝向_Cs(坐标结构_3 目标, 朝向结构_2& 角度, 朝向结构_2& 角度差){  FLOAT FOV_x = *(FLOAT*)((DWORD)GetModuleHandleA("hl.exe") + 0x195fe58);  FLOAT FOV_y = *(FLOAT*)((DWORD)GetModuleHandleA("hl.exe") + 0x195fe5C);  FLOAT FOV_z = *(FLOAT*)((DWORD)GetModuleHandleA("hl.exe") + 0x195fe60);  FLOAT 水平朝向 = *(FLOAT*)((DWORD)GetModuleHandleA("hl.exe") + 0x19E10C8);  FLOAT 高低朝向 = *(FLOAT*)((DWORD)GetModuleHandleA("hl.exe") + 0x19E10C4);    if (目标.x > FOV_x && 目标.y >= FOV_y)//第一象限  {    角度.水平朝向 = (FLOAT)((double)atan2(目标.y - FOV_y, 目标.x - FOV_x) * 180 / 3.1415);  }  if (目标.x <= FOV_x && 目标.y > FOV_y)//第二象限  {    角度.水平朝向 = 180 - (FLOAT)((double)atan2(目标.y - FOV_y, FOV_x - 目标.x) * 180 / 3.1415);  }  if (目标.x < FOV_x && 目标.y <= FOV_y)//第三象限  {    角度.水平朝向 = 180 + (FLOAT)((double)atan2(FOV_y - 目标.y, FOV_x - 目标.x) * 180 / 3.1415);  }  if (目标.x >= FOV_x && 目标.y < FOV_y)//第四象限  {    角度.水平朝向 = 360 - (FLOAT)((double)atan2(FOV_y - 目标.y, 目标.x - FOV_x) * 180 / 3.1415);  }  FLOAT 平面距离 = sqrt((目标.x - FOV_x) * (目标.x - FOV_x) + (FOV_y - 目标.y) * (FOV_y - 目标.y));  if (目标.z > FOV_z)//上方  {    角度.高低朝向 = (FLOAT)(-(double)atan2(目标.z - FOV_z, 平面距离) * 180 / 3.1415);  }  if (目标.z < FOV_z)//下方  {    角度.高低朝向  = (FLOAT)((double)atan2(FOV_z - 目标.z, 平面距离) * 180 / 3.1415);  }

  角度差.水平朝向 = 水平朝向 - 角度.水平朝向;   if (角度差.水平朝向 <= -180)//跨0轴的两种情况  {    角度差.水平朝向 += 360;  }  if (角度差.水平朝向 >= 180)  {    角度差.水平朝向 -= 360;  }
  角度差.高低朝向 = 角度.高低朝向 - 高低朝向;}

          绘制类

万事俱备,我们来写个绘制类

数据全部完毕我们开始写代码绘制了,这部分是固定的代码,很简单

完全中文编程,代码已经很清晰了

大家跟着我敲写一遍即可

当然第一种 是最粗糙的, 由奢入简易  由俭入奢易难 我们先来粗糙的

struct 坐标结构_2{    float x, y;};struct 坐标结构_3{    float x, y, z;};struct 坐标结构_4{    float x, y, z, w;};
class 绘制{public:    HWND m_窗口句柄;    RECT m_窗口;    int m_分辨率宽;    int m_分辨率高;    RECT m_外窗口;    int m_外窗口宽;    int m_外窗口高;    float m_矩阵[16];    DWORD m_矩阵地址; public:    绘制(HWND 窗口句柄, DWORD 矩阵地址);    绘制()    {    }private:    void 取窗口信息();public:    void 绘制矩形(HDC HDC句柄, HBRUSH 画刷句柄, int x, int y, int w, int h);    void 画框(HDC HDC句柄, HBRUSH 画刷句柄, int x, int y, int w, int h, int 厚度);    void 画线(HDC HDC句柄, int X, int Y);//Q2217777779探讨    void 绘制字符串(HDC HDC句柄, int x, int y, COLORREF color,  const char* text);    //还没讲矩阵绘制  这个函数请忽略    //bool 世界坐标转屏幕坐标(坐标结构_3 游戏坐标, 坐标结构_2& 屏幕坐标);    bool 世界坐标转屏幕坐标_非矩阵(坐标结构_2& 屏幕坐标, FLOAT  水平角度差, FLOAT 高低角度差);//传递真实角度};

绘制::绘制(HWND 窗口句柄, DWORD 矩阵地址){    m_窗口句柄 = 窗口句柄;    m_矩阵地址 = 矩阵地址;    memcpy(&m_矩阵, (PBYTE*)(m_矩阵地址), sizeof(m_矩阵));    取窗口信息();}
void 绘制::取窗口信息(){    GetClientRect(m_窗口句柄, &m_窗口);    m_分辨率宽 = m_窗口.right - m_窗口.left;    m_分辨率高 = m_窗口.bottom - m_窗口.top;    GetWindowRect(m_窗口句柄, &m_外窗口);  //含有边框及全屏幕坐标    m_外窗口宽 = m_外窗口.right - m_外窗口.left;    m_外窗口高 = m_外窗口.bottom - m_外窗口.top;}
void 绘制::绘制矩形(HDC HDC句柄, HBRUSH 画刷句柄, int x, int y, int w, int h){    RECT 矩形 = { x,y,x + w,y + h };    FillRect(HDC句柄, &矩形, 画刷句柄);//绘制矩形}
void 绘制::画框(HDC HDC句柄, HBRUSH 画刷句柄, int x, int y, int w, int h, int 厚度){    绘制矩形(HDC句柄, 画刷句柄, x, y, w, 厚度);//顶边    绘制矩形(HDC句柄, 画刷句柄, x, y + 厚度, 厚度, h - 厚度);//左边    绘制矩形(HDC句柄, 画刷句柄, (x + w) - 厚度, y + 厚度, 厚度, h - 厚度);//右边    绘制矩形(HDC句柄, 画刷句柄, x + 厚度, y + h - 厚度, w - 厚度 * 2, 厚度);//底边}
void 绘制::画线(HDC HDC句柄, int X, int Y){    取窗口信息();    MoveToEx(HDC句柄, m_分辨率宽 / 2, m_分辨率高, NULL);    LineTo(HDC句柄, X, Y);}
HFONT 字体属性;void 绘制::绘制字符串(HDC HDC句柄, int x, int y, COLORREF color, const char* text){    SetTextAlign(HDC句柄, TA_CENTER | TA_NOUPDATECP);    SetBkColor(HDC句柄, RGB(0, 0, 0));    SetBkMode(HDC句柄, TRANSPARENT);    SetTextColor(HDC句柄, color);    SelectObject(HDC句柄, 字体属性);    TextOutA(HDC句柄, x, y, text, strlen(text));    DeleteObject(字体属性);}

//还没讲矩阵绘制  这个函数请忽略/*bool 绘制::世界坐标转屏幕坐标(坐标结构_3 游戏坐标, 坐标结构_2& 屏幕坐标){    取窗口信息();    memcpy(&m_矩阵, (PBYTE*)(m_矩阵地址), sizeof(m_矩阵));    坐标结构_4 裁剪坐标;    裁剪坐标.x = 游戏坐标.x * m_矩阵[0] + 游戏坐标.y * m_矩阵[4] + 游戏坐标.z * m_矩阵[8] + m_矩阵[12];    裁剪坐标.y = 游戏坐标.x * m_矩阵[1] + 游戏坐标.y * m_矩阵[5] + 游戏坐标.z * m_矩阵[9] + m_矩阵[13];    裁剪坐标.z = 游戏坐标.x * m_矩阵[2] + 游戏坐标.y * m_矩阵[6] + 游戏坐标.z * m_矩阵[10] + m_矩阵[14];    裁剪坐标.w = 游戏坐标.x * m_矩阵[3] + 游戏坐标.y * m_矩阵[7] + 游戏坐标.z * m_矩阵[11] + m_矩阵[15];
    if (裁剪坐标.w < 0.0f)        return false;
    坐标结构_3 NDC;    NDC.x = 裁剪坐标.x / 裁剪坐标.w;    NDC.y = 裁剪坐标.y / 裁剪坐标.w;    NDC.z = 裁剪坐标.z / 裁剪坐标.w;
    屏幕坐标.x = (m_分辨率宽 / 2 * NDC.x) + m_分辨率宽 / 2;    屏幕坐标.y = -(m_分辨率高 / 2 * NDC.y) + m_分辨率高 / 2;    return true;}*/
bool 绘制::世界坐标转屏幕坐标_非矩阵(坐标结构_2& 屏幕坐标, FLOAT  水平角度差, FLOAT 高低角度差){               取窗口信息();    FLOAT 高低可视角度 = (FLOAT)((double)atan2(m_分辨率高, m_分辨率宽)*180/3.1415);    if (fabs(水平角度差) > 45 || fabs(高低角度差) > 高低可视角度)    {        return false;// 不在屏幕范围内   
    int 水平差 = (int)(tan(水平角度差 * 3.1416 / 180) * ((m_分辨率宽) / 2));     屏幕坐标.x = (float)(m_分辨率宽 / 2 + 水平差);    // 哪里不懂可以v  hdlw312    int 高度差 = (int)(tan(高低角度差 * 3.1416 / 180) * ((m_分辨率宽) / 2));    屏幕坐标.y = (float)(m_分辨率高 / 2 + 高度差);
    return true;}

到这里  非矩阵绘制的所有准备工作都已经完毕了    ,就看你要怎么调用了

          非矩阵绘制方框

编写循环绘制代码

通过上面的编写 我们终于可以调用观看效果了

HBRUSH 画刷句柄;HDC HDC句柄;COLORREF 文本颜色_亮红 = RGB(255, 0, 0);COLORREF 文本颜色_红 = RGB(128, 0, 0);COLORREF 文本颜色_黄 = RGB(200, 200, 0);COLORREF 文本颜色_黑 = RGB(0, 0, 0);COLORREF 文本颜色_白 = RGB(255, 255, 255);COLORREF 文本颜色_常用 = RGB(158, 255, 0);坐标结构_2 屏幕坐标_j;坐标结构_2 屏幕坐标_H;周围对象  周围;
int mainThread(){
    绘制 FPS_绘制(FindWindow(L"Valve001", 0), (DWORD)GetModuleHandleA("hl.exe") + 0x1820100);

    while (true)    {        HDC句柄 = GetDC(FPS_绘制.m_窗口句柄);        周围.刷新周围数据_Cs();        for (int i = 0; i < (int)周围.对象数量; i++)        {            if (周围.对象列表[i].死亡标志位 == 1)            {                continue;            }            if (FPS_绘制.世界坐标转屏幕坐标_非矩阵(屏幕坐标_j, 周围.对象列表[i].角度差_j.水平朝向, 周围.对象列表[i].角度差_j.高低朝向))            {                if (FPS_绘制.世界坐标转屏幕坐标_非矩阵(屏幕坐标_H, 周围.对象列表[i].角度差_H.水平朝向, 周围.对象列表[i].角度差_H.高低朝向))                {                    if (周围.对象列表[i].阵营 == 2)                    {                        画刷句柄 = CreateSolidBrush(文本颜色_常用);                    }                    else                    {                        画刷句柄 = CreateSolidBrush(文本颜色_亮红);                    }
                    float 高度 = 屏幕坐标_j.y - 屏幕坐标_H.y;                    float 宽度 = 高度 / 2;                                           FPS_绘制.画框(HDC句柄, 画刷句柄, (int)(屏幕坐标_H.x - 宽度/2), (int)屏幕坐标_H.y-2, (int)宽度, (int)(高度+4), 1);                    DeleteObject(画刷句柄);
                    char healthChar[255];                    sprintf_s(healthChar, sizeof(healthChar), "%d", 周围.对象列表[i].Hp);                    FPS_绘制.绘制字符串(HDC句柄, (int)屏幕坐标_j.x, (int)屏幕坐标_j.y, 文本颜色_常用, healthChar);                    if (GetKeyState(VK_F2) & 1)                    {                        FPS_绘制.画线(HDC句柄, (int)屏幕坐标_j.x, (int)屏幕坐标_j.y);                    }                }            }        }        Sleep(1);        DeleteObject(HDC句柄);    }}
BOOL APIENTRY DllMain(HMODULE hModule,    DWORD  ul_reason_for_call,    LPVOID lpReserved){    switch (ul_reason_for_call)    {    case DLL_PROCESS_ATTACH:        DisableThreadLibraryCalls(hModule);        CreateThread(0, 0, (LPTHREAD_START_ROUTINE)mainThread, 0, 0, 0);
    case DLL_THREAD_ATTACH:    case DLL_THREAD_DETACH:    case DLL_PROCESS_DETACH:        break;    }    return TRUE;}

if (GetKeyState(VK_F2) & 1)

这里面学习一个快捷键指令 GetKeyState

每次按下切换他的状态   大于0 或则小于0

从而可以实现开关的功能

微调

发现效果是有了,但是并不精准

优点是:不需要矩阵 ,完全的数学公式可以计算出来,而且公式是固定的,

缺点:

但是不精准

有可视角的关系,运算的误差等等导致的,算出来的误差虽然不是特别大

而且也可以通过调整的方式让其精确,但是不够完美.

我们可以选择微调可视角度,微调屏幕X,Y坐标等等方式来处理

反正这种方法想要完全的精密是不太可能的

但是达到基本效果是没问题的,虽然达到精密没有那么的重要,但是完美主义者

是一定会使用矩阵来计算的

那么我们继续学习矩阵,用矩阵的方式  绘制方框

          绘制的几种方法和反外挂建议

我们非矩阵的方式绘制 是没有那么的精确的

在学习矩阵绘制之前,我们先来了解下绘制的几种方法

第一种 hook  d3d/opengl

优点:不闪 ,代码简单

缺点:非常容易被检测

第二种 窗口上自行绘制,但是会闪

优缺点适中

第三种 自建透明窗口,覆盖游戏窗口,透明窗口上绘制

优点:稳定

确定:代码复杂,会闪

反外挂:无非就是针对外挂使用的函数进行检测

第四种,分屏透视,一个窗口正常游戏,另外一个窗口显示透视信息,主播一般用的

不要说很少一部分,我对陪玩代练主播等了解较多,大部分都是这样.

反外挂思路,同样是针对实现原理,很轻松的应对

只是后2种 属于外部绘制,检测比较困难

下面我们对几种方法一一了解

          深入学习矩阵 (总结所有游戏矩阵特点 找到矩阵)

对象的世界坐标列向量

x

y

z

w(w为了兼容4*4矩阵以及可以NDC坐标转化而设计存在的,大家可以暂且不管)

可以通过被一个游戏矩阵(以后我们就叫他游戏矩阵吧)  乘  从而获得 剪辑坐标,也可以叫裁剪坐标,都是翻译而来(暂且当成屏幕坐标也没问题,因为他到屏幕坐标转换及其简单)

在学习这个矩阵之前呢我们先来了解

行主序和列主序

行主序就是拿4*4矩阵的行来* 后面的矩阵,我们前面的例子  都是这样

a1  a2  a3  a4      *     x    =    a1 *x+ a2*y+ a3*z + a4*w

b1  b2  b3   b4            y          b1 *x+ b2*y+ b3*z + b4*w

c1   c2   c3   c4           z          c1 *x+ c2*y+ c3*z + c4*w

d1  d2   d3   d4            w        d1 *x+ d2*y+ d3*z + d4*w

而列主序就是 拿4*4 矩阵的列 来* 后面的矩阵,这个前面没有出现过

a1  b1  c1  d1     *     x    =    a1 *x+ a2*y+ a3*z + a4*w

a2  b2  c2   d2           y          b1 *x+ b2*y+ b3*z + b4*w

a3   b3  c3  d3           z          c1 *x+ c2*y+ c3*z + c4*w

a4  b4  c4   d4            w        d1 *x+ d2*y+ d3*z + d4*w

那么我们现在了解下这个游戏矩阵都能对我们干嘛,给他拆分成几个功能

最后他是这几个功能的结合体而已

缩放位移矩阵

如果是行主序矩阵的话就是

(1不是固定的  应该写成Tw   )

如果是列主序矩阵的话就是

Sx  0    0    0

0    Sy  0    0

0    0    Sz  0

Tx  Ty  Tz   Tw

x

y

z

w

等于

x*Sx+w*Tx

y*Sy+w*Ty

z*Sz+w*Tz

w*Tw

对矩阵   x  y  z  w  进行了  缩放和位移

所以我们在这里知道   矩阵移动影响的主要是

Tx  Ty  Tz  Tw

结论一: 我们在走路做位移的情况下,行主序最后一列  列主序最后一行 Tx  Ty  Tz  Tw 会改变

当然不一定只有走路的时候会变

原因很简单,这个矩阵是各种操作的结合体

如以下例子:

   

只改变Y的情况下

只改变X的情况下

只改变Z的情况下

XYZ混合改变的情况下

   

旋转矩阵

简单证明下结论

假设旋转45度

用极限验证法,验证结果正确

围绕Z轴 转动

结论二:只转动水平朝向的时候  行主序第三列不变 ,列主序第三行 不变化

列主序如下

围绕X 轴转动

结论三:只转动高低朝向的时候  行主序第一行不变 ,列主序第一列 不变化

列主序如下

围绕Y 轴转动  我们没有这种情况的时候

开倍镜

总结矩阵6条结论

第一结论:   行主序最后一列   列主序最后一行 走路 跳的状态会改变,不代表别的动作不改变

第二结论:   水平转动的情况  行主序第三列不变   列主序的话第三行不变

第三结论:   高低朝向改变的时候   行主序第一行不变   列主序的第一列不变

第四结论:   矩阵第一个值  -1 到1 的, 这个绝对吗  不绝对!

第五结论:   行主序 第一个行第3个元素 是固定的0    列主序 第一列的第三个元素是0  不绝对!

第六结论:   我们开倍镜 第一个值  会* 相应的倍数  不绝对!

通过找到的矩阵可以进一步完善结论

通过以上的规律  CE 搜到如下矩阵地址

矩阵地址

hl.exe+1820100

这个过程文字很啰嗦,只能视频里见了,无非根据以上结论 扫描而已

如果这个都扫不到,说明CE基础薄弱,看看公众号任鸟飞逆向学习下免费CE基础

          世界坐标 ->剪辑坐标->NDC坐标->屏幕坐标

那么我们来了解下坐标的转换过程

通过以上的矩阵

世界坐标  ----> 剪辑坐标

矩阵乘法

a0    a1     a2     a 3

a4    a5    a6     a7

a8     a9    a10   a11

a12   a13  a14    a15

*

x

y

z

w

得到:

剪辑坐标 x  = a0*x +a4*y + a8*z + a12*w

剪辑坐标 y  = a1*x +a5*y + a9*z + a13*w

剪辑坐标 z  = a2*x +a6*y + a10*z + a14*w

剪辑坐标 w  = a3*x +a7*y + a11*z + a15*w

世界坐标按照我们之前的矩阵法则转换成了平面的剪辑坐标

但是剪辑坐标是和分辨率没有直接关系的 ,需要我们进一步转换

剪辑坐标坐标系如下  正中心为0,0

剪辑坐标---->NDC坐标

矩阵的设计中w 是可以让剪辑坐标范围到-1和1的  也就成了NDC坐标

所以NDC坐标很好理解,就是 -1到1的平面坐标系     中心点为0,0

NDC .x =  剪辑坐标 x/剪辑坐标 w

NDC.y  =剪辑坐标y/剪辑坐标 w

NDC.z  =剪辑坐标z/剪辑坐标 w

DNC坐标---->屏幕坐标

有了DNC坐标我们可以很容易转换成屏幕坐标了

当然屏幕坐标不止一种形式

我们现在只以CS为例

后面会有其他类型

DNC.x / 1   =  屏幕坐标差.x  /    分辨率_宽 /2

屏幕坐标差.x   =  (分辨率_宽 /2) * DNC.x

屏幕坐标.x  =  (分辨率_宽 /2) * DNC.x    +  分辨率_宽/2

DNC.y / 1   =  屏幕坐标差.y  /    分辨率_高 /2

屏幕坐标差.y =   -(分辨率_高 /2)*DNC.y

屏幕坐标.y=   -(分辨率_高 /2)*DNC.y + 分辨率_高 /2

          矩阵绘制

最终代码如下

再用之前的代码   只把世界坐标转屏幕坐标_非矩阵() 改成世界坐标转屏幕坐标()  就可以实现功能了

其实也是我们之前的代码设计的比较合理

这样 只要替换一句命令,其他指令完全不影响

bool 绘制::世界坐标转屏幕坐标(坐标结构_3 游戏坐标, 坐标结构_2& 屏幕坐标){    取窗口信息();    memcpy(&m_矩阵, (PBYTE*)(m_矩阵地址), sizeof(m_矩阵));    坐标结构_4 裁剪坐标;    裁剪坐标.x = 游戏坐标.x * m_矩阵[0] + 游戏坐标.y * m_矩阵[4] + 游戏坐标.z * m_矩阵[8] + m_矩阵[12];    裁剪坐标.y = 游戏坐标.x * m_矩阵[1] + 游戏坐标.y * m_矩阵[5] + 游戏坐标.z * m_矩阵[9] + m_矩阵[13];    裁剪坐标.z = 游戏坐标.x * m_矩阵[2] + 游戏坐标.y * m_矩阵[6] + 游戏坐标.z * m_矩阵[10] + m_矩阵[14];    裁剪坐标.w = 游戏坐标.x * m_矩阵[3] + 游戏坐标.y * m_矩阵[7] + 游戏坐标.z * m_矩阵[11] + m_矩阵[15];
    if (裁剪坐标.w < 0.0f)        return false;
    坐标结构_3 NDC;    NDC.x = 裁剪坐标.x / 裁剪坐标.w;    NDC.y = 裁剪坐标.y / 裁剪坐标.w;    NDC.z = 裁剪坐标.z / 裁剪坐标.w;
    屏幕坐标.x = (m_分辨率宽 / 2 * NDC.x) + m_分辨率宽 / 2;    屏幕坐标.y = -(m_分辨率高 / 2 * NDC.y) + m_分辨率高 / 2;    return true;}

这次不用微调就已经 精确了吧?

所以证明矩阵确实算的比较准确而且帮我们省掉了大量计算的时间

到这里,你的基础算过了

迎接后面真正的学习吧!

          单机口袋西游和单机突袭进一步熟悉相关数据,矩阵,以及绘制

游戏不分类型

RPG也好,回合制也好,FPS也好

其实只是一个操作的区别而已,本质没有任何区别

FPS的自瞄相当于Rpg的朝向怪物

FPS的绘制相当于RPG的计算碰撞体  以方便躲避 或则自写寻路等等

所以

看到RPG 这样 意外吗?

所以要明白本质是一样的,没有区别

口袋西游:

用到的数据很简单,跟着2020的课程很容易学会。

这是一款普通的RPG类游戏,不设计到射击相关的功能,我们只是拿来做画框绘制练习。

人物数据:

[[[单机版基地址]+1C]+28]+3C X

[[[单机版基地址]+1C]+28]+40 Y

[[[单机版基地址]+1C]+28]+44 Z

[[[单机版基地址]+1C]+28]+560 X中心点

[[[单机版基地址]+1C]+28]+564 Y中心店

[[[单机版基地址]+1C]+28]+568 Z中心点

[[[单机版基地址]+1C]+28]+56C X中心点到对顶点的X差

[[[单机版基地址]+1C]+28]+570 Y中心点到对顶点的Y差

[[[单机版基地址]+1C]+28]+574 Z中心点到对顶点的Z差

[[[单机版基地址]+1C]+28]+578 立方体下顶点X坐标

[[[单机版基地址]+1C]+28]+57C 立方体下顶点Y坐标

[[[单机版基地址]+1C]+28]+580 立方体下顶点Z坐标

[[[单机版基地址]+1C]+28]+584 立方体上顶点X坐标

[[[单机版基地址]+1C]+28]+588 立方体上顶点Y坐标

[[[单机版基地址]+1C]+28]+58C 立方体上顶点Z坐标

周围遍历

[[[[[单机版基地址]+1C]+8]+20]+5C]  对象数量

[[[[[[[单机版基地址]+1C]+8]+20]+58]+N*4]+4]+138 周围对象血量(未被攻击过是0)

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+3C 周围对象X

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+40 周围对象Z

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+44 周围对象Y

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+ED 死亡标志1为死亡BYTE

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2C4 X中心点

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2C8 Z中心店

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2CC Y中心点

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2D0 X中心点到对顶点的X差

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2D4 Y中心点到对顶点的Y差

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2D8 Z中心点到对顶点的Z差

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2DC 立方体下顶点X坐标

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2E0 立方体下顶点Y坐标

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2E4 立方体下顶点Z坐标

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2E8 立方体上顶点X坐标

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2EC 立方体上顶点Y坐标

[[[[[[[单机版基地址]+1C]+8]+20]+1C]+N*4]+4]+2F0 立方体上顶点Z坐标

突袭:

这是一款类似于CS的单机游戏,在设计上相对于CS更加简单一些,数据也比较简单,适合新手练习。

这款游戏也可以联网,有一些数据需要在联网状态下才能精确的分析出来,在新课程中也做了详细的讲解。

人物数据:

[0x00587C0C]+28   X   

[0x00587C0C]+2C  Y

[0x00587C0C]+30   Z

[0x00587C0C]+34   水平朝向顺时针递增360  

[0x00587C0C]+38   竖直朝向-90到90   从下到上

[0x00587C0C]+EC    角色血量

周围遍历

00587C18     玩家数量

[[00587C10]+n*4]+4   X_head

[[00587C10]+n*4]+8   Y_head

[[00587C10]+n*4]+C   Z_head

[[00587C10]+n*4]+28   X   

[[00587C10]+n*4]+2C  Y

[[00587C10]+n*4]+30   Z

[[00587C10]+n*4]+50   身高

[[00587C10]+n*4]+34   水平朝向顺时针递增360  

[[00587C10]+n*4]+38   竖直朝向-90到90   从下到上

[[00587C10]+n*4]+EC    角色血量

在周围遍历中我们发现多了一个头部坐标,这也是其相对于CS设计的更加完善的地方。

游戏不分类型

RPG也好,回合制也好,FPS也好。

其实只是一个操作的区别而已,本质没有任何区别。

FPS的自瞄相当于Rpg的朝向怪物。

FPS的绘制相当于RPG的计算碰撞体  以方便躲避 或则自写寻路等等。

通过已有的数据,我们可以实现以下效果。

看到RPG游戏这样意外吗?

因为游戏很古老,引擎也很古老,所以能够得到资源只允许我们做一个3D的立方体模型,

但是不管如何,RPG和FPS的本质是一样的,没有任何区别,只是我们研究的角度不同,要达到的效果也不同。

矩阵

下面我们来看看这两款游戏的矩阵数据

口袋西游矩阵  [D2E5D0]+E8

突袭矩阵   57AFE0

这都是标准的4*4矩阵,具体的分析方法在上面我们已经提到了,下面我们来看看封装数据的过程。

数据封装

DWORD 口袋西游_周围基地址 = 0xD0DF1C;void 周围对象::刷新周围数据_口袋西游(){  DWORD Temp = *(DWORD*)(*(DWORD*)(*(DWORD*)(*(DWORD*)口袋西游_周围基地址 + 0x1C) + 0x8) + 0x20);  对象数量 = *(DWORD*)(Temp +  0x5C);  DWORD 怪物基地址 = *(DWORD*)(Temp + 0x58);  for (int i = 0; i < (int)对象数量; i++)  {    对象列表[i].X_j = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x2DC);    对象列表[i].Y_j = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x2E0);    对象列表[i].Z_j = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x2E4);    对象列表[i].X_H = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x2E8);    对象列表[i].Y_H = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x2EC);    对象列表[i].Z_H = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x2F0);    对象列表[i].阵营 = 1;    对象列表[i].Hp = *(DWORD*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x138);    对象列表[i].死亡标志位 = 0;      }  对象数量++;  对象列表[对象数量-1].X_j = *(FLOAT*)(*(DWORD*)(*(DWORD*)(*(DWORD*)0xD0DF1C + 0x1C) + 0x28) + 0x578);  对象列表[对象数量-1].Y_j = *(FLOAT*)(*(DWORD*)(*(DWORD*)(*(DWORD*)0xD0DF1C + 0x1C) + 0x28) + 0x57C);  对象列表[对象数量-1].Z_j = *(FLOAT*)(*(DWORD*)(*(DWORD*)(*(DWORD*)0xD0DF1C + 0x1C) + 0x28) + 0x580);  对象列表[对象数量-1].X_H = *(FLOAT*)(*(DWORD*)(*(DWORD*)(*(DWORD*)0xD0DF1C + 0x1C) + 0x28) + 0x584);  对象列表[对象数量-1].Y_H = *(FLOAT*)(*(DWORD*)(*(DWORD*)(*(DWORD*)0xD0DF1C + 0x1C) + 0x28) + 0x588);  对象列表[对象数量-1].Z_H = *(FLOAT*)(*(DWORD*)(*(DWORD*)(*(DWORD*)0xD0DF1C + 0x1C) + 0x28) + 0x58C);  对象列表[对象数量-1].阵营 = 2;  对象列表[对象数量-1].Hp = *(DWORD*)(*(DWORD*)(*(DWORD*)(*(DWORD*)0xD0DF1C + 0x1C) + 0x28) + 0x288);  对象列表[对象数量-1].死亡标志位 = 0;}
DWORD 突袭_周围基地址 = 0x00587C10;void 周围对象::刷新周围数据_突袭(){  对象数量 = *(DWORD*)0x00587C18;  DWORD 怪物基地址 = *(DWORD*)突袭_周围基地址;  for (int i = 1; i < (int)对象数量; i++)  {    对象列表[i].X_j = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x28);    对象列表[i].Y_j = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x2C);    对象列表[i].Z_j = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x30);    对象列表[i].X_H = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x4);    对象列表[i].Y_H = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0x8);    对象列表[i].Z_H = *(FLOAT*)(*(DWORD*)(怪物基地址 + 0x4 * i) + 0xC);    对象列表[i].阵营 = 2;    对象列表[i].Hp = 100;    对象列表[i].死亡标志位 = 0;  }}

基本数据的封装,我们单独来完成,而后面的整体调用我们也进行了设计和修改,可以同时支持西游,CS和突袭这3款游戏。

HBRUSH 画刷句柄;HDC HDC句柄;COLORREF 文本颜色_亮红 = RGB(255, 0, 0);COLORREF 文本颜色_红 = RGB(128, 0, 0);COLORREF 文本颜色_黄 = RGB(200, 200, 0);COLORREF 文本颜色_黑 = RGB(0, 0, 0);COLORREF 文本颜色_白 = RGB(255, 255, 255);COLORREF 文本颜色_常用 = RGB(158, 255, 0);坐标结构_2 屏幕坐标_j;坐标结构_2 屏幕坐标_H;周围对象  周围;DWORD 编号 = 0;绘制  FPS_绘制;


int mainThread(){      if (FindWindow(L"Valve001", 0) != 0)    {        绘制 FPS_绘制_2(FindWindow(L"Valve001", 0), (DWORD)GetModuleHandleA("hl.exe") + 0x1820100);        FPS_绘制 = FPS_绘制_2;        编号 = 1;    }    else if (FindWindow(L"XYElementClient Window", 0) != 0)    {        绘制 FPS_绘制_2(FindWindow(L"XYElementClient Window", 0), *(DWORD*)0xD2E5D0 + 0xE8);        FPS_绘制 = FPS_绘制_2;        编号 = 2;    }    else    {        绘制 FPS_绘制_2(FindWindow(0, L"AssaultCube"), 0x57AFE0);        FPS_绘制 = FPS_绘制_2;        编号 = 3;    }    while (true)    {        HDC句柄 = GetDC(FPS_绘制.m_窗口句柄);        if (编号 == 1)        {            周围.刷新周围数据_Cs();        }        if (编号 == 2)        {            周围.刷新周围数据_口袋西游();        }        if (编号 == 3)        {            周围.刷新周围数据_突袭();        }        for (int i = 0; i < (int)周围.对象数量; i++)        {            if (周围.对象列表[i].死亡标志位== 1)            {                continue;            }            坐标结构_3 坐标_j = { 周围.对象列表[i].X_j ,周围.对象列表[i].Y_j ,周围.对象列表[i].Z_j };      if (FPS_绘制.世界坐标转屏幕坐标(坐标_j, 屏幕坐标_j))            //if (FPS_绘制.世界坐标转屏幕坐标_非矩阵(屏幕坐标_j, 周围.对象列表[i].角度差_j.水平朝向, 周围.对象列表[i].角度差_j.高低朝向))      {                             坐标结构_3 坐标_H = { 周围.对象列表[i].X_H ,周围.对象列表[i].Y_H ,周围.对象列表[i].Z_H };        if (FPS_绘制.世界坐标转屏幕坐标(坐标_H, 屏幕坐标_H))               // if (FPS_绘制.世界坐标转屏幕坐标_非矩阵(屏幕坐标_H, 周围.对象列表[i].角度差_H.水平朝向, 周围.对象列表[i].角度差_H.高低朝向))        {                                                      if (周围.对象列表[i].阵营 == 2)                    {                        画刷句柄 = CreateSolidBrush(文本颜色_常用);                    }                    else                    {                        画刷句柄 = CreateSolidBrush(文本颜色_亮红);                    }
                    if (编号 == 1 || 编号 == 3)                    {            float head = 屏幕坐标_H.y - 屏幕坐标_j.y;//  负数高度            float width = head / 2;   //  负数宽度            float center = width / -2;  //  一半宽度            float extra = head / -6;   //  三分之一 宽度            FPS_绘制.画框(HDC句柄, 画刷句柄, (int)(屏幕坐标_j.x + center), (int)屏幕坐标_j.y, (int)width, (int)(head - extra), 1);                    }                    if (编号 == 2)                    {                        口袋西游模型                        坐标结构_2 屏幕点1, 屏幕点2, 屏幕点3, 屏幕点4, 屏幕点5, 屏幕点6, 屏幕点7, 屏幕点8;                        //点求的顺序不能乱,我就不简化代码了                        坐标结构_3 点1 = { 周围.对象列表[i].X_j ,周围.对象列表[i].Y_j ,周围.对象列表[i].Z_j };                        坐标结构_3 点2 = { 周围.对象列表[i].X_H ,周围.对象列表[i].Y_j ,周围.对象列表[i].Z_j };                        坐标结构_3 点3 = { 周围.对象列表[i].X_H ,周围.对象列表[i].Y_H ,周围.对象列表[i].Z_j };                        坐标结构_3 点4 = { 周围.对象列表[i].X_j ,周围.对象列表[i].Y_H ,周围.对象列表[i].Z_j };                        坐标结构_3 点5 = { 周围.对象列表[i].X_j ,周围.对象列表[i].Y_j ,周围.对象列表[i].Z_H };                        坐标结构_3 点6 = { 周围.对象列表[i].X_H ,周围.对象列表[i].Y_j ,周围.对象列表[i].Z_H };                        坐标结构_3 点7 = { 周围.对象列表[i].X_H ,周围.对象列表[i].Y_H ,周围.对象列表[i].Z_H };                        坐标结构_3 点8 = { 周围.对象列表[i].X_j ,周围.对象列表[i].Y_H ,周围.对象列表[i].Z_H };                        if (FPS_绘制.世界坐标转屏幕坐标(点1, 屏幕点1) == 0) continue;                        if (FPS_绘制.世界坐标转屏幕坐标(点1, 屏幕点1) == 0) continue;                        if (FPS_绘制.世界坐标转屏幕坐标(点2, 屏幕点2) == 0) continue;                        if (FPS_绘制.世界坐标转屏幕坐标(点3, 屏幕点3) == 0) continue;                        if (FPS_绘制.世界坐标转屏幕坐标(点4, 屏幕点4) == 0) continue;                        if (FPS_绘制.世界坐标转屏幕坐标(点5, 屏幕点5) == 0) continue;                        if (FPS_绘制.世界坐标转屏幕坐标(点6, 屏幕点6) == 0) continue;                        if (FPS_绘制.世界坐标转屏幕坐标(点7, 屏幕点7) == 0) continue;                        if (FPS_绘制.世界坐标转屏幕坐标(点8, 屏幕点8) == 0) continue;                        //  我连的脑袋疼,就不简化代码了                        FPS_绘制.两点画线(HDC句柄, 屏幕点1.x, 屏幕点1.y, 屏幕点2.x, 屏幕点2.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点2.x, 屏幕点2.y, 屏幕点3.x, 屏幕点3.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点3.x, 屏幕点3.y, 屏幕点4.x, 屏幕点4.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点4.x, 屏幕点4.y, 屏幕点1.x, 屏幕点1.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点5.x, 屏幕点5.y, 屏幕点6.x, 屏幕点6.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点6.x, 屏幕点6.y, 屏幕点7.x, 屏幕点7.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点7.x, 屏幕点7.y, 屏幕点8.x, 屏幕点8.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点8.x, 屏幕点8.y, 屏幕点5.x, 屏幕点5.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点1.x, 屏幕点1.y, 屏幕点5.x, 屏幕点5.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点2.x, 屏幕点2.y, 屏幕点6.x, 屏幕点6.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点3.x, 屏幕点3.y, 屏幕点7.x, 屏幕点7.y);                        FPS_绘制.两点画线(HDC句柄, 屏幕点4.x, 屏幕点4.y, 屏幕点8.x, 屏幕点8.y);                        ///                    }
                    DeleteObject(画刷句柄);
          char healthChar[255];          sprintf_s(healthChar, sizeof(healthChar), "%d", 周围.对象列表[i].Hp);                    FPS_绘制.绘制字符串(HDC句柄,(int)屏幕坐标_j.x, (int)屏幕坐标_j.y, 文本颜色_常用,  healthChar);                         if (GetKeyState(VK_F2) & 1)          {                        FPS_绘制.画线(HDC句柄, (int)屏幕坐标_j.x, (int)屏幕坐标_j.y);          }        }      }             }        Sleep(1);        DeleteObject(HDC句柄);    }}


          骨骼绘制和模型绘制

不同的游戏,不同的引擎,骨骼的设计理念也不同,找到的数据和最终达到的效果也不同。

新款的引擎相对于老款引擎设计的骨骼也更加的细腻,完善。

骨骼不一定有绝对坐标,但是一般都有相对坐标。

比如口袋西游,Y坐标是高度,

他的骨骼由一个中心点,和两个固定的顶点组成,还需要结合朝向来决定立方体在空间的方位

中心点 X0,Y0,Z0

底点 X1,Y1,Z1

顶点 X2,Y2,Z2

下面是定点坐标的计算公式推算

距离 =  sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));

蓝 =   距离/2

tanA  =   (y2-y1)/(x2-x1) = 绿/蓝

绿  =   距离/2*(y2-y1)/(x2-x1)

粉  =  sqrt(蓝平方 +  绿平方 )

紫 = 蓝 - 绿

橙 = 紫*  蓝/粉

黑 = 绿* 橙/ 蓝

右下坐标:

x = 粉+黑

y = - 橙

灰 / (距离/2  + 绿)  =  蓝/ 粉

灰 = (距离/2  + 绿)  * 蓝/ 粉

勾股定理求粉的右部分

黄 = 粉 - 粉的右分部

左上坐标:

x = 黄

y = 灰

但是这个里面不用这么复杂的计算,如果仔细的观察数据变化,可以发现它是

X,Y等比增量  说明他是 45°角度固定的

所有 8个点

逆时针顺序分别是

底点 (X1,Y1,Z1)

点1:X1,Y1,Z1

点2:X2,Y1,Z1

点3:X2,Y2,Z1

点4:X1,Y2,Z1

顶点 (X2,Y2,Z2)

点5:X1,Y1,Z2

点6:X2,Y1,Z2

点7:X2,Y2,Z2

点8:X1,Y2,Z2

连线方式

1-2   2-3  3-4  4-1

5-6  6-7  7-8  8-5

1-5  2-6  3-7  4 -8

这样做起来就容易多了。

           自瞄

自瞄第一步,自然是分清敌我,

绘制和瞄准队友是毫无意义的。

在前面代码中添加一断阵营判断,

如下

DWORD 自己阵营 = *(BYTE*)(Cs_周围基地址2 + 0x68 * 0 + 0x4E);

对象列表[对象数量].阵营 = *(BYTE*)(Cs_周围基地址2 + 0x68 * i + 0x4E);    if (对象列表[对象数量].阵营 == 自己阵营)    {      对象列表[对象数量].阵营 = 2;    }    else// 公众号任鸟飞逆向    {      对象列表[对象数量].阵营 = 1;    }

常量是几  ,自己定,方便调用即可

第二步

自瞄应该设置成距离准星最近的敌人,而不是距离人物最近的

所以我们要计算一下 准星最近敌人

添加一个成员

这里面我们就粗略计算,大家可以自己用三角函数计算的更精确,不赘述了

if (fabs(对象列表[对象数量].角度差_H.水平朝向) < 45 && fabs(对象列表[对象数量].角度差_H.高低朝向) < 35&& 对象列表[对象数量].阵营==1&& 对象列表[对象数量].死亡标志位!=1){  if (fabs(对象列表[对象数量].角度差_H.水平朝向) + fabs(对象列表[对象数量].角度差_H.高低朝向) < 最小角度和)  {     最小角度和 = fabs(对象列表[对象数量].角度差_H.水平朝向) + fabs(对象列表[对象数量].角度差_H.高低朝向);     准星最近对象 = 对象列表[对象数量];         }      }

这样最近的就计算出来了,当然我们只是简单的算和, 应该根据角度 算距离的

第三步

我们就可以到  循环线程加入自瞄代码了

把计算出来的最近朝向写入到  我们的准星数据中

高低朝向值 hl.exe+19E10C4 //从低到高  89  到  -89

水平朝向值 hl.exe+19E10C8 //  逆时针 从 0 到 360

  if (GetKeyState(VK_F3) & 1)//启动自瞄        {            if (编号 == 1)            {                *(FLOAT*)((DWORD)GetModuleHandleA("hl.exe") + 0x19E10C4) = 周围.准星最近对象.角度_H.高低朝向;                * (FLOAT*)((DWORD)GetModuleHandleA("hl.exe") + 0x19E10C8) = 周围.准星最近对象.角度_H.水平朝向;                                   }        }
        Sleep(1);        DeleteObject(HDC句柄);

但是准星秒在敌人头顶了

我们之前为了画框   其实给的坐标  是不够准确的

我们调整下,自瞄的坐标

另外视野内没有敌人,我们也要判断一下

这样就可以枪枪爆头,甩狙等等骚操作都可以了

          UE4引擎学习

吃鸡模拟器为例

根据ue4引擎世界对象数组的特点,我们可以采取以下方式来进行扫描,

打中敌人数组对象+1,拿出手雷+1或者+2,

也就是说出现新物品+不定数量,例如手雷增加,原本手雷不在模型上显示,打出的子弹也是有对象的,所以子弹打中某个碰撞体也会增加。

[["BattleRoyaleTrainer-Win64-Shipping.exe"+2AF0FB8]  +138 ]+ b8

[["BattleRoyaleTrainer-Win64-Shipping.exe"+2AF0FB8 ] +30 ]+ b8

坐标数组

CTRL+A

X                       Y                    Z     00000000

3F800000  3F800000  3F800000   00000000

Q2217777779

?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 00 00 00 00  00 00 80 3F  00 00 80 3F  00 00 80 3F  00 00 00 00

UE4  矩阵特征

00 00 00 00  00 00 00 00    00 00 80 3F   00 00 80 3F

00 00 80 3F  00 00 80 3F   00 00 00 00  00 00 00 00

00 00 80 3F   00 00 00 00  00 00 00 00   00 00 80 3F

00 00 80 3F    00 00 00 00

UE4的矩阵有他自己的特点

下访问断

r8+280

返回

rbx+280

来源于上面某个CALL

F7 往里追

[["BattleRoyaleTrainer-Win64-Shipping.exe"+2ADA268]+24D08*8+8] +280

也可以扫描   rbx+280的 rbx 然后挨个下断

"BattleRoyaleTrainer-Win64-Shipping.exe"+2887630

[["BattleRoyaleTrainer-Win64-Shipping.exe"+2887630]+ 3*8 +8]+280

深入继续学习可以 公众号 任鸟飞逆向  进行讨论学习

跳或则沿着 XY轴走,2个值改变,乱走路3个值改变。

第一个值范围是-1.19到1.19,准星可以放大绝对值是否确定?

这是一个列主序的矩阵,

第三列 0  0  0 1,其中的1可能是其他数值,

第一列的第三3个数值为0。

实战操作部分  公众号任鸟飞逆向视频中提供试看 和分享

游戏中画面受限制不能上传

Unity3D游戏

u3d游戏的数据有一个特点,那就是公式特别长,所以很多人都不习惯直接取分析,而是通过dnspy等工具去正向分析。这类分析方式我们放到下一个专题来说,下面我们看看u3d数据。

1.朝向

这是从无模块的代码开始分析出来的角色朝向,简单的一个朝向数据,用了一个二叉树,和十多层的偏移。

在分析过程中还有各种坑需要我们跳,比如在函数头部加入一些远跳转,以达到隐藏偏移的目的。

2.周围对象及骨骼遍历

这款u3d单机游戏的骨骼是以数组的形式挂在周围对象下的,通过一些字符串以及ID来确定骨骼的类型,并且其坐标是绝对坐标,而非像UE4一样的相对坐标。

这里是获取骨骼刚体信息的关键代码,而周围对象的遍历方式也是以数组套数组层层嵌套的方式获取的,这也是u3d代码的特点。

在每一层数组对象的固定偏移内,会有该对象的信息字符串,可能是名字,也可能是类型

只要找到了基地址,我们就可以通过这些字符串来确定每一层对象的信息,并完成整体的遍历。

u3d的数据我们做一个简单的了解即可,目前主流的引擎更加偏向于UE4等虚幻引擎。

          第二种透视方式  HOOK OPENGL/ D3D 方式 透视

了解OpenGL 和D3D

OpenGL 开放式图形库, 具有非常强的可移植性,CS中 OpenGL 模式比D3D模式效果好

Direct3D 简称D3D,由微软公司所制定的3D规格界面 与windows兼容性更好,微软亲儿子,优点 游戏运行效率更高

3D绘图 和渲染 用到他们其中之一

/* BeginMode */

#define GL_POINTS                         0x0000

#define GL_LINES                          0x0001

#define GL_LINE_LOOP                      0x0002

#define GL_LINE_STRIP                     0x0003

#define GL_TRIANGLES                      0x0004

#define GL_TRIANGLE_STRIP                 0x0005

#define GL_TRIANGLE_FAN                   0x0006

#define GL_QUADS                          0x0007

#define GL_QUAD_STRIP                     0x0008

#define GL_POLYGON                        0x0009

GL_POINTS:把每一个顶点作为一个点进行处理,顶点n即定义了点n,共绘制N个点

GL_LINES:把每一个顶点作为一个独立的线段,顶点2n-1和2n之间的是第n条线段,总共绘制N/2条线段

GL_LINE_STRIP:绘制从第一个顶点到最后一个顶点依次相连的一组线段,第n和n+1个顶点定义了第n线段,总共绘制 N-1条线段

GL_LINE_LOOP:绘制从第一个顶点到最后一个顶点依次相连的一组线段,然后最后一个顶点和第一个顶点相连,第n和n+1个顶点定义了线段n,总共绘制N条线段

GL_TRIANGLES:把每个顶点作为一个独立的三角形,顶点3n-2、3n-1和3n定义了第n个三角形,总共绘制N/3个三角形

GL_TRIANGLE_STRIP:绘制一组相连的三角形,对于奇数n,顶点n、n+1和n+2定义了第n个三角形;对于偶数n,顶点n+1、n和n+2定义了第n个三角形,总共绘制N-2个三角形

GL_TRIANGLE_FAN:绘制一组相连的三角形,三角形是由第一个顶点及其后给定的顶点确定,顶点1、n+1和n+2定义了第n个三角形,总共N-2个三角形

GL_QUADS:绘制由四个顶点组成的一组单独的四边形。顶点4n-3、4n-2、4n-1和4n定义了第n个四边形。总共绘制N/4个四边形

GL_QUAD_STRIP:绘制一组相连的四边形。每个四边形是由一对顶点及其后给定的一对顶点共同确定的。顶点2n-1、2n、2n+2和2n+1定义了第n个四边形,总共绘制N/2-1个四边形

GL_POLYGON:绘制一个凸多边形。顶点1到n定义了这个多边形。

#define GL_DEPTH_TEST                     0x0B71

如果启用,则进行深度比较并更新深度缓冲区。参见glDepthFunc和glDepthRange。

拿OPENGL 为例

首先知道两个函数  openGL32.DLL 中的 glBegin(枚举模式) 

glDisable(ID) 关闭渲染,关闭服务器端GL功能

我们 Hook  glBegin ,判断是否是人物ID,如果是, 直接让他调用 glDisable 关闭渲染,就可以实现透视

OD 附加 CS

直接跳到   opengl32 模块的   glBegin 函数

参数是   渲染ID

当ID 等于 5或则6的时候是 人物, 当然你可以 每个ID  都测试下看看是什么,种类并不多

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

几何基础,多种矩阵的学习,世界坐标到屏幕坐标的两种转换方法,三种绘制方框的原理,hookd3d,hookopengl,骨骼透视,主播的秘密,FPS各种BT功能的原理 和检测对抗原理,UE4引擎,U3D 的相关文章

  • Node.js 连接 MongoDB

    在 Node js 中连接 MongoDB 数据库需要使用第三方模块 mongodb 首先需要安装 mongodb 模块 你可以使用 npm 命令来安装 npm install mongodb 接着可以使用以下代码来连接 MongoDB 数
  • Qt 可视化Ui设计

    QMainWindow 是主窗口类 主窗口类具有主菜单栏 工具栏和状态栏 类似于一般的应用程序的主窗口 QWidget是所有具有可视界面类的基类 选择QWidget创建的界面对各种界面组件都可以支持 QDialog是对话框类 可建立一个基于
  • 两种方法(JS方法和Vue方法)实现页面渲染

    一 需求 根据数据渲染如下页面 二 JS方法 div class box w div class box hd h3 精品推荐 h3 a href 查看全部 a div div class box bd ul class clearfix
  • JavaScript之ES6规范中let的巧用(经典案例讲解)

    html部分 ul li 天 li li 地 li li 人 li li 和 li ul ul li dyklk li ul 要求 再点击某个li标签的时候 弹框输出其对应的顺序号 注意 本文js代码部分全为原生写法 错误写法 var oL

随机推荐