欢迎光临~啸龙游戏工作室官方网站!
语言选择:繁體中文

声明:本站仅作SEO测试使用,如有侵犯您的合法权益,请在线留言,我们将在7个工作日处理完毕。谢谢您的理解与支持

您的位置:主页 > 游戏资讯 >

游戏资讯

以CSGO透视为例研究,实现FPS游戏的方框透视!

时间:2022-03-22 23:59--来源:网络-- 作者:啸龙 --点击量:73

今天给大家带来的是关于“以CSGO透视为例研究,实现FPS游戏的方框透视!”的资讯,让我们一起看看吧

以CSGO透视为例研究,实现FPS游戏的方框透视

FPS游戏一直全是游戏中的受欢迎,在诸多稀奇古怪的外挂软件中,透视能够 说成最基础的作用,这在其中,V社受欢迎的CS可以说被苦不堪言,从初期CS1.6的侧门穿烟到如今的CSGO的小陀螺大陀螺图片,殊不知其透视的基本原理是一脉相承的,说简易些,每一游戏手机客户端都务必了解对手的部位,仅仅 在具体游戏中依照游戏体制对游戏玩家给予显示信息,可是,这种对手的部位信息内容,终究是储放在手机客户端—游戏玩家的电脑.

虽然各种游戏拥有自身的反挂对策,可是终究是在游戏玩家的客场上,欠佳游戏玩家只必须寻找储放的内存中的对手的座标,根据GDI绘图方框,便能够 保持最基础的透视,由于这一全过程只能载入内存,而沒有载入,因此透视不易被杜绝.

针对CSGO,由于V社游戏中广泛常有人物高亮的控制面板命令,只必须在手机客户端上将操纵高亮的内存标值改动,乃至都省掉了绘图方框的全过程(以下图)—实际上,这类舞弊方法在18年很广泛.

CSGO人物高亮輔助在这一产业链中,习惯性把对内存开展载入和改动的挂称为外挂软件,把仅限载入内存的称为輔助(脚本制作),实际上业界早就达成协议:輔助就是说外挂软件,套入B站UP主右小死得话:

内存挂奸污游戏,非内存挂性侵游戏,不论是外挂软件和輔助,都对游戏导致了挥之不去的损害.解析巨头都用C++,像我这类菜鸡只适用C#,在上一篇文章中根据读写能力内存的实际操作,保持了一个最基础的植物大战僵尸的修改器,本期以CSGO的方框透视为例,只涉及读内存,不涉及到写.

在本文最终我能把一个双板透视的demo源代码放出去,可是我毫无疑问并不是让大家拿来当仙人的,这也只是是一个最基础的demo罢了,我所有的实践活动所有是在线下bot局里边,都没有对多的人pk开展过影响.因此即使你拿来多的人,毫无疑问都是会被ban掉的.

最先导入我写的一个内存读写能力的类.上一期做植物大战僵尸修改器的情况下的作法是每载入/改动一次内存就获得一次程序流程句柄,实际操作出来这模样极为消耗内存.在遍历CSGO的目标链表的情况下必须消耗1.5秒上下的時间,因此我将好多个方式改成必须实例化再应用,也就是说全部案例目标直至最终销户都只能一个句柄的存有.

class Memory { #region DLL方法引入 /// <summary> /// 从内存中读取字节集 /// </summary> /// <param name="hProcess">进程句柄</param> /// <param name="lpBaseAddress">内存基质</param> /// <param name="lpBuffer">读取到缓存区的指针</param> /// <param name="nSize">缓存区大小</param> /// <param name="lpNumberOfBytesRead">读取长度</param> /// <returns></returns> [DllImport("kernel32.dll")] private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, IntPtr lpNumberOfBytesRead); /// <summary> /// 从内存中写入字节集 /// </summary> /// <param name="hProcess">进程句柄</param> /// <param name="lpBaseAddress">内存地址</param> /// <param name="lpBuffer">需要写入的数据</param> /// <param name="nSize">写入字节大小,比如int32是4个字节</param> /// <param name="lpNumberOfBytesWritten">写入长度</param> /// <returns></returns> [DllImport("kernel32.dll")] private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, int[] lpBuffer, int nSize, IntPtr lpNumberOfBytesWritten); //以现有进程获取句柄 [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); //关闭句柄 [DllImport("Kernel32.dll")] private static extern void CloseHandle(IntPtr hObject); #endregion IntPtr Process_Handle; int PID; static byte[] buffer = new byte[4]; static float[] Singles = new float[1]; static IntPtr byteADDR = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0); public Memory(string PName) { PID = Get_PID(PName); Process_Handle = OpenProcess(0x001F0FFF, false, PID); } /// <summary> /// 得到模块基质 /// </summary> /// <param name="PName">进程名</param> /// <param name="DllName">动态链接库名</param> /// <returns></returns> public IntPtr GetModuleHandle(string DllName) { try { var P = Process.GetProcessById(PID); for (int i = 0; i < P.Modules.Count; i++) { if (P.Modules[i].ModuleName == DllName) { return P.Modules[i].BaseAddress; } } } catch (Exception e) { } return (IntPtr)0; } //以进程名得到进程ID static int Get_PID(string PName) { try { var process = Process.GetProcessesByName(PName); return process[0].Id; } catch { return 0; } } /// <summary> /// 读取内存内容(整数型) /// </summary> /// <param name="BaseADDR">内存地址</param> /// <param name="PName">进程名</param> /// <returns>内存值</returns> public int Read_MemoryInt32(int ADDR) { //将基质内存中的值读入缓冲区 var Rs = ReadProcessMemory(Process_Handle, (IntPtr)ADDR, byteADDR, 4, IntPtr.Zero); return Rs ? Marshal.ReadInt32(byteADDR):-1; } /// <summary> /// 读取内存内容(浮点型) /// </summary> /// <param name="BaseADDR">内存地址</param> /// <param name="PName">进程名</param> /// <returns>内存值</returns> public float Read_MemoryFloat(int ADDR) { //将基质内存中的值读入缓冲区 var Rs = ReadProcessMemory(Process_Handle, (IntPtr)ADDR, byteADDR, 4, IntPtr.Zero); Marshal.Copy(byteADDR, Singles, 0, 1); return Rs ? Singles[0]: -1; } /// <summary> /// 将数值写入内存地址 /// </summary> /// <param name="PName">进程名</param> /// <param name="ADDR">内存地址</param> /// <param name="Value">待写入数值</param> /// <returns></returns> public bool Write_MemoryValue(int ADDR, int Value) { var Rs = WriteProcessMemory(Process_Handle, (IntPtr)ADDR, new int[] { Value }, 4, IntPtr.Zero); return Rs; } /// <summary> /// 得到实际操作地址 /// </summary> /// <param name="PName">进程名</param> /// <param name="BaseADDR">基质</param> /// <param name="Deviations">偏移量</param> /// <returns></returns> public int Get_RealityADDR(int BaseADDR, string[] Deviations) { var RealityADDR = Read_MemoryInt32(BaseADDR); for (int i = 0; i < Deviations.Length - 1; i++) { RealityADDR = Read_MemoryInt32(RealityADDR + Convert.ToInt32(Deviations[i], 16)); } RealityADDR = RealityADDR + Convert.ToInt32(Deviations.Last(), 16); return RealityADDR; } public void Close() { CloseHandle(Process_Handle); } 随后人们打开CSGO,开展一场bot试练,然后开启CE开展人物的坐标检索,我就是先检索的血量,随后再用运行内存电脑浏览器立即寻找的人物的坐标,用编程设计的观念来想会很一切正常,如同给你搭建一个人物类,你大几率都是坐标血量这种人物属性放同一个类里边,因此在运行内存中,她们隔得不容易很远,对于如何使用CE找血量基质这种我不会做过多阐释,这儿仅仅 科学研究原理,不探讨运用. 找到的人物属性基质以下 根据不断的搜索核对,最后获得的了血条,三维座标和视角矩阵和人物的势力(图片中我忽略了团队属性),视角矩阵在物理学中表达你的眼光所至(我就是文科生,或许说得不精确),在游戏里你能单纯性了解为照相机视线.CSGO的视角矩阵为44,不一样游戏不一样模块不一样,有的将会是43或是3*4这类. 因此这儿人们能够 界定一个人物属性的建筑结构 struct Person_Struct { public int Health; //CT(警)3,T(匪)2 public int army; public float X, Y, Z; }CSGO中目标的储放是一个链表的结构,在大一所学的数据信息结构中,人们了解只必须寻找随意的一个链表节点,就能够 遍历出全部链表.因此如今人们早已找到存储自身人物角色的节点,根据CE的结构分析,人们能够 获得这模样的一个链表结构 struct Obj_Struct{ public int Self_ADDR;//当前对象实例存放地址 public int ID;//节点ID public int Pre, Next;//上一个节点/下一个节点 } 实现

有了这些,可以遍历出所有节点的对象. 先得到链表头,当然,你也可以找链尾或者同时找.只是这样子比较麻烦.

Obj_Struct Get_First_Node(Obj_Struct Any_Node) { var First_Node = Any_Node; while (First_Node.Pre != 0) { First_Node = Init_Struct(First_Node.Pre); } return First_Node; }

得到链头之后定义一个定时器,每10毫秒轮询一遍,获取最新信息.

void Timer_Call(object state) { if (!Timer_State) { Timer_State = true; var Rs_List = new List<Person_Struct>(); var Index_Node = (Obj_Struct)state; var self = Init_Person(Init_Struct(DllPtr + self_offset).Self_ADDR); while (Index_Node.Next != 0) { Index_Node = Init_Struct(Index_Node.Next); var To_Person = Init_Person(Index_Node.Self_ADDR); // && if (To_Person.Health > 0 && !new float[] { To_Person.X, To_Person.Y, To_Person.Z }.Contains(0)) { if (To_Person.X != self.X) { Rs_List.Add(To_Person); } //Console.WriteLine(To_Person.Health); } } Draw_Rectangle(self,Rs_List); Person_List = Rs_List; Timer_State = false; } }这在其中,每一对象试着去获取它的血量,由于链表中不仅储放着人物,很将会也有一些无关紧要的物品,比如说跑来跑去的雏鸡,枪支这些.一些对象乃至有血量,可是却不一定有人物的坐标,因此假如出現血量不以0且坐标不以(0,0,0)的,那麼就能够 评定它是一个生存人物(死尸血量也为0,就无需显示信息了),评定后,获取其坐标,随后利用WorldToScreen涵数变换为显示屏坐标.WorldToScreen说白了,获取总体目标对象当今的全球坐标系部位,并将其变换为显示屏坐标系的点. ViewScreen WorldToScreen(Person_Struct Ps,float[,] ViewMatrix) { var Sigma_Matrix = new float[4]; for (int i = 0; i < Sigma_Matrix.Length; i++) { Sigma_Matrix[i] = ViewMatrix[i, 0] * Ps.X + ViewMatrix[i, 1] * Ps.Y + ViewMatrix[i, 2] * Ps.Z + ViewMatrix[i, 3]; } if (Sigma_Matrix[3] < 0.01) { return new ViewScreen() {X = 0,Y=0}; } float X = Sigma_Matrix[0] / Sigma_Matrix[3]; float Y = Sigma_Matrix[1] / Sigma_Matrix[3]; float Z = Sigma_Matrix[2] / Sigma_Matrix[3]; float Window_H = 1080 / 2;//屏幕高度 float Window_W = 1920 / 2;//屏幕宽度 ViewScreen Rs; Rs.X = X * Window_W + Window_W + X; Rs.Y = -(Y * Window_H) + Window_H + Y; return Rs; }

得到人物在屏幕上的坐标就简单的了.最后就是使用GDI+绘制方框了,这里一定要记得绘制出所有人物的坐标后再进行下一轮,而非绘制一个人即refresh,否则即使开启了双缓冲,闪烁也会很严重.使用GDI+绘制的函数:

void Draw_Rectangle(Person_Struct self,List<Person
游戏外挂
_Struct> Other_list) { //求距离 try { var Rectangles = new List<(Rectangle,Color)>(); foreach (var Other in Other_list) { var VM = WorldToScreen(Other, Get_ViewMatrix(DllPtr + ViewMatrix_offset)); double M = Math.Sqrt(Math.Pow((double)(self.X - Other.X), 2) + Math.Pow((double)(self.Y - Other.Y), 2) + Math.Pow((double)(self.Z - Other.Z), 2)) / 30; int H = Convert.ToInt32(950 / M * 2); int W = Convert.ToInt32(400 / M * 2); if (Other.army == self.army) { Rectangles.Add((new Rectangle((int)VM.X - W / 2, (int)VM.Y - H, W, H), Color.Coral)); } else { Rectangles.Add((new Rectangle((int)VM.X - W / 2, (int)VM.Y - H, W, H), Color.Red)); } } Form1.bitmap_form.Drawing(Rectangles); } catch { } } public void Drawing(List<(Rectangle rectangle,Color line_color)> Rectangles) { try { var MyBuff = new BufferedGraphicsContext().Allocate(CreateGraphics(), new Rectangle(Location.X, Location.Y, Width, Height)); var e = MyBuff.Graphics; e.Clear(BackColor); foreach (var rectangles in Rectangles) { var pen = new Pen(rectangles.line_color, 3); e.DrawRectangle(pen, rectangles.rectangle); } MyBuff.Render(); MyBuff.Dispose(); } catch { } } 这2个涵数的效果是,同势力人物应用橙色描绘,对手应用红色描绘,自然,你可以绘图上人物血条护甲和外挂软件中普遍的放射线这些.因为GDI+的高效率较低,因此一定要打开双缓存,那样才不容易有闪动的状况出現. 假如上边流程进行圆满得话,大约能够 做到以下效果: 因为CSGO只能对手暴露位置,或是间距对手足够近的情况下,才会将其目标载入,且当其身亡或是再度掩藏位置一段时间,他的信息会保存直到下一次暴露位置或是更新情况.因此必须做足够的判断,不可以做到其他手机游戏那类全局性透視的实际效果. 接下去,你必须做进一步的判断与调节,例如判断开倍镜尺寸来调节框架尺寸(由于间距位置不变可是角度引流矩阵发生变化),随后来判断滞留好久没有情况更新的人物(自然要区别老六). 因为我不是玩CSGO的,因此即便有透視打电脑上都是很菜hhh.
好了,这就是关于“以CSGO透视为例研究,实现FPS游戏的方框透视!”的相关资讯,感谢你们的观看
用手机扫描二维码关闭
二维码