0.首先还是先展示一下运行效果

1.做外挂的起因

  玩过几次QQ对对碰游戏,感觉挺好玩。玩着玩着实然心血来潮,打算做一个外挂出现。简单想了一下,感觉可以实现:)。然后就决定把它做出来。带着兴趣带着好奇心带着求知欲,让我们一起出发吧。

2.简单分析一下这个游戏的操作流程

     a.登录QQ游戏大厅,b.打开对对碰游戏,c.选桌位,d.点击开始,e.等待其它玩家,f.正式开始游戏,g.寻找合适的图标进行交换、h.游戏结束,重复d,e,f,g的动作。

     我们这个外挂主要要完成什么的任务呢,应该是g这一步吧。当然最好也可以实现d,e,f.那样外挂用起来就更方便了。

3.寻找解决问题的办法

  经过简单分析我们已经明确了我们这个外挂的目标了,那应该如何实现呢?对于一个从来没有做过外挂的没有一点经验的,我们应该从何入手呢?先说说d的这步操作吧,“点击开始”,如果我们自己做过程序的话,我们肯定会想着要是可以动态的去调用一下这个游戏窗口的这个按钮的单击事件就好了。但是感觉好难是吧,但是我相信是可行的。但是我没有研究这种方法,这种方法可能在VC编程中使用的多。我是用程序模拟鼠标点击“开始”按钮,实现了这个自动开始的功能。

模拟鼠标单击的代码如下:

模拟鼠标单击

[DllImport("user32.dll")]
public static extern bool SetCursorPos(int X, int Y);
[DllImport(
"user32.dll")]
public static extern void mouse_event(MouseEventFlag flags, int dx, int dy, uint data, UIntPtr extraInfo);
[Flags]
public enum MouseEventFlag : uint
{
Move
= 0x0001,
LeftDown
= 0x0002,
LeftUp
= 0x0004,
RightDown
= 0x0008,
RightUp
= 0x0010,
MiddleDown
= 0x0020,
MiddleUp
= 0x0040,
XDown
= 0x0080,
XUp
= 0x0100,
Wheel
= 0x0800,
VirtualDesk
= 0x4000,
Absolute
= 0x8000
}

 模拟鼠标单击就可以实现了,那是不是在对对碰的过程中也可以使用这种方法来进行操作呢?答案是肯定的。但是有一个难点,就是我们要确定到底应该是哪两个图标进行交换。把这个问题具体话就是一个8*8的数组中找出相邻的两个交换后可以组成三个或者三个以上相连的说明这两个就是可以交换的。具体应该怎么找请看以下代码:

View Code

1 //交换后,检查x,y位置的行和列 是否可以连成三个或者三个以上,如果是返回就是true
2  public Boolean CanChange(int x,int y, int[,] arr)
3 {
4 int v = arr[x, y];
5 int count = 1;
6 int oldx=x;
7 int oldy=y;
8 //x--
9   while(x > 0)
10 {
11 x--;
12 if (arr[x, y] == v)
13 {
14 count++;
15 }
16 else
17 {
18 break;
19 }
20 }
21 if (count >= 3) return true;
22 else
23 {
24 x = oldx;
25 //x++
26   while (x < 7)
27 {
28 x++;
29 if (arr[x, y] == v)
30 {
31 count++;
32 }
33 else
34 {
35 break;
36 }
37 }
38 if (count >= 3) return true;
39 }
40
41 count = 1;
42 x = oldx;
43 //y--
44   while (y > 0)
45 {
46 y--;
47 if (arr[x, y] == v)
48 {
49 count++;
50 }
51 else
52 {
53 break;
54 }
55 }
56 if (count >= 3) return true;
57 else
58 {
59 y = oldy;
60 //y++
61   while (y < 7)
62 {
63 y++;
64 if (arr[x, y] == v)
65 {
66 count++;
67 }
68 else
69 {
70 break;
71 }
72 }
73 if (count >= 3) return true;
74 }
75 return false;
76 }

其实在这个过程还有一个问题我们没有解决,就是如何把图标转成等价的数组的呢?不知道你有没有做过截图这样的程序,就是用程序把当前屏幕捕获下来。如果你做过,你肯定会想到把游戏界面取出来,然后进行分析。分析也是要方法的,如果你对每种图标的所有像素都要做个对照那就惨了,速度肯定很P。这里因为图标是有限的,是规则的,那么我们完全可以读取每个图标的一个特征像素点来区分出他们可是什么.这特征像素点应该选择在哪呢?当图标有道具的时候你发现道具会显示在图标的右下角,很显然这个特征点是不能选择到右下角的,那我们就选择右上角的一个点。。经过多次测试,得到了一个比较合适的相对点,相对图标右上角的位置为(15,30),对不同的图标有着不同的值。然后我就可以根据得到每个图标的这个点的值得到一个适合解决问题的数组了。把得到的像素转换成一个整数放到数据中。这个数据转换是这样的。

         R     G     B

    /// 152,088,040 monkey –>1
    /// 248,204,000 chicken  –>2
    /// 152,156,152 cat        –>3
    /// 248,248,248 fox        –>4
    /// 232,192,080 cow       –>5
    /// 248,252,248 pandan  –>6
    /// 056,244,064 frog       –>0

把得到的数据转换成整数放入要操作的数组中。在转换成整数的过程可能得到的点不是这里边的点,那说明界面被别的窗体遮挡了或者游戏中的图标在运动着,或者被别人使用了道具。那么当出现这种错误的时候就要放弃本次扫描。等待定时器的下次描述。(使用Timer定时里扫描游戏界面)当完整的得到这个数组后,通过遍历数据分别测试下上交换左右交换如果可以,就模拟鼠标点击,进行图标交换。

最后再粘贴一些必要的代码吧(遍历数组的,模拟鼠标点击的,以及发道具的)

//截图程序
public static MemoryStream MS = null;
    /*屏幕截图*/
    [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
    private static extern bool BitBlt(
    IntPtr hdcDest, //目标设备的句柄 
    int nXDest, // 目标对象的左上角的X坐标 
    int nYDest, // 目标对象的左上角的X坐标 
    int nWidth, // 目标对象的矩形的宽度 
    int nHeight, // 目标对象的矩形的长度 
    IntPtr hdcSrc, // 源设备的句柄 
    int nXSrc, // 源对象的左上角的X坐标 
    int nYSrc, // 源对象的左上角的X坐标 
    System.Int32 dwRop);// 光栅的操作值
    [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
    private static extern IntPtr CreateDC(
    string lpszDriver, // 驱动名称 
    string lpszDevice, // 设备名称 
    string lpszOutput, // 无用,可以设定位"NULL" 
    IntPtr lpInitData); // 任意的打印机数据 
    private Bitmap MyImage = null;
// 矩形结构 
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

public static Bitmap GetByRect(RECT rc)
    {

        IntPtr dc1 = CreateDC("DISPLAY", null, null, (IntPtr)null);
        //创建显示器的DC 
        Graphics g1 = Graphics.FromHdc(dc1);
        //由一个指定设备的句柄创建一个新的Graphics对象 
         Bitmap MyImage = null;
         if (rc.right - rc.left <= 0 || rc.bottom - rc.top <= 0)
         {
             return null;
         }
         else
         {
             MyImage = new Bitmap(rc.right - rc.left, rc.bottom - rc.top, g1); 
         }
        //根据屏幕大小创建一个与之相同大小的Bitmap对象 
        Graphics g2 = Graphics.FromImage(MyImage);
        //获得屏幕的句柄 
        IntPtr dc3 = g1.GetHdc();
        //获得位图的句柄 
        IntPtr dc2 = g2.GetHdc();
        //把当前屏幕捕获到位图对象中 
        BitBlt(dc2, 0, 0, rc.right-rc.left, rc.bottom-rc.top, dc3, rc.left, rc.top, 13369376);
        //把当前屏幕拷贝到位图中 
        g1.ReleaseHdc(dc3);
        //释放屏幕句柄 
        g2.ReleaseHdc(dc2);
        //释放位图句柄 

        return MyImage;
    }


//得到当前游戏对应的数据数组

int[,] arr = new int[8, 8];
            for (int i = 0; i < 8; i++)
            {
                for (int j = 0; j < 8; j++)
                {
                    Color c = bitmap.GetPixel(268+(j * 48) + 15, 94+(i * 48) + 30);//bitmap是截图得到的位图
                    arr[i, j] = GameTool.converColorToInt(c);//把该像素转换成一个整数的数
                    if (arr[i, j] == -1)//转换的值不在已知的图标内就结束扫描
                    {
                        return;
                    }
                }
            }

//测试得到可以交换的图标位置
//rc为得到的游戏窗口的位置信息
public void TestAll(RECT rc,int[,] arr)
        {
            bool flag = false;
            
//下上交换
            for (int i = 7; i >0 && !flag; i--)
            {
                for (int j = 0; j < 8; j++)
                {
                    if (arr[i,j]!=arr[i-1,j]&&TestPosDownToUP( i,j,  i - 1,j, arr))
                    {
                        //点击鼠标
                        trueChange(269+rc.left + j * 48 + 20,94+rc.top + i * 48 + 20,269+rc.left + j * 48 + 20,   94+rc.top + (i - 1) * 48 + 20);
                        flag = true;
                        break;
                    }
                }
            }
//左右交换
            for (int i = 1; i < 8 && !flag; i++)
            {
                for (int j = 7; j >=0; j--)
                {
                    if (arr[j, i] != arr[j, i - 1] && TestPosDownToUP(j, i, j, i - 1, arr))
                    {
                        //点击鼠标
                        trueChange(269 + rc.left + i * 48 + 20, 94 + rc.top + j * 48 + 20, 269 + rc.left + (i - 1) * 48 + 20, 94 + rc.top + j * 48 + 20);
                        flag = true;
                        break;
                    }
                }
            }
        }

//模拟鼠标点击(我把鼠标操作放到了WinTool这个类里了)
 public void trueChange(int x,int y,int x2,int y2)
        {
            Point oldpos =  Control.MousePosition;//保存一下点击前的位置

            WinTool.SetCursorPos(x, y);//移动
            WinTool.mouse_event(WinTool.MouseEventFlag.LeftDown | WinTool.MouseEventFlag.LeftUp, 0, 0, 0, UIntPtr.Zero);//左键单击
            WinTool.SetCursorPos(x2, y2);
            WinTool.mouse_event(WinTool.MouseEventFlag.LeftDown | WinTool.MouseEventFlag.LeftUp, 0, 0, 0, UIntPtr.Zero);
            WinTool.SetCursorPos(oldpos.X, oldpos.Y);//恢复位置
        }

        //做一次交换测试
        public Boolean TestPosDownToUP(int dx, int dy, int ux, int uy, int[,] arr)
        {
            //交换一下数据(破坏了数据)
            int tem = arr[dx, dy];
            arr[dx, dy] = arr[ux, uy];
            arr[ux, uy] = tem;
            //一点向四周找
            if (CanChange(dx, dy, arr) || CanChange(ux, uy, arr))
                return true;
            else
            {
                //恢复数据
                arr[ux, uy] = arr[dx, dy];
                arr[dx, dy] = tem;
                return false;
            }
        }

最后把基本的功能实现后,你会发现还有一些要处理,例如发道具,检测当前哪个位置上有玩家等。也是通过读取相应位置的像素点来得到当前玩家信息,然后根据道具的好坏模拟按按键,代码如下:

  System.Windows.Forms.SendKeys.Send("2");// 模拟按2
  System.Windows.Forms.SendKeys.Flush();

这些功能应该没有什么难点了吧。。对了,还有一个功能就是实现全局热键,可以参考这篇文章

http://www.tansea.cn/article.asp?id=114

回头看看自己写的东西。好乱呀。呵呵。。像我写的代码似的。。大家将就看吧。。请多指点。有不少人说通过调整内存的方法,确实很好,我也想过,但是总感觉找内存位置是一个大难题。这种方法也太残忍,容易封号。

编译好的程序对对碰助手

如果你想得到源代码,不用我告诉你怎么做了吧。。我想你懂的。。呵呵。。

版权声明:本文为weirhp原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/weirhp/archive/2011/04/10/2011064.html