首页 > 辅助资讯 > 卡盟平台官网

以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.