Android天天飞车游戏辅助系统

声明:单纯的辅助模拟人操作,非修改游戏内存,非修改游戏运行文件,非修改调用库……

(避免使用外挂这个词,毕竟离外挂还有好远的距离~)

玩了好久的腾讯天天飞车,每天的20局任务相当蛋疼,遂头脑一热想开发个Android应用,来模拟手动操作。

花了几天时间,把Android开发环境搭建了,搞了个简单的能识别当前游戏处在哪个界面,并模拟操作点击左右转向按键,马马虎虎算是可以完成每日20局任务了~

摸索新领域总是充满未知和惊喜的,遂记之如下。

整体思路

通过屏幕像素点的颜色信息,判断当前处于哪个界面(弱爆了的感觉~~~~~)

然后模拟按键操作~~~

手机:移动M701 ROOT

一、如何搭建Android开发环境

度娘之~

二、如何创建浮动窗口

因为考虑到不能影响到正常使用其他应用,需要将控制按钮放在浮动于所有应用之上的窗口上。

先要在OnCreate()函数里初始化如下变量:

private static WindowManager wm =(WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);private static WindowManager.LayoutParamsparams = new WindowManager.LayoutParams();               public ViewGroup controlViewGroup =(ViewGroup) View.inflate(getApplicationContext(), R.layout.floating, null);


 

创建悬浮窗的代码:

 

/** * 创建可移动的悬浮窗,在所有应用最顶层显示 */private void createFloatView(){         Log.i(TAG,TAG+"createFloatView()");                 btn_floatView= new Button(getApplicationContext());         btn_floatView.setBackgroundResource(R.drawable.img_float);          //设置window type         params.type= WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;         params.format= PixelFormat.RGBA_8888; // 设置图片格式,效果为背景透明         //设置Window flag         params.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL                            |WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ;         params.flags&= ~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; // 屏幕常亮仅在控制板可见时有效         params.alpha= CONTROL_VIEW_ALPHA; // 设置悬浮窗的透明度                     //设置悬浮窗的长得宽         params.width= FLOATING_VIEW_SIZE;         params.height= FLOATING_VIEW_SIZE;          //设置悬浮窗的Touch监听//可以实现按住拖放的功能,单击后,弹出控制窗口         btn_floatView.setOnTouchListener(newOnTouchListener()         {                   intlastX, lastY;                   intparamX, paramY;                   intupX,upY;                    publicboolean onTouch(View v, MotionEvent event)                   {                            switch(event.getAction())                            {                            caseMotionEvent.ACTION_DOWN:                                     lastX= (int) event.getRawX();                                     lastY= (int) event.getRawY();                                     paramX= params.x;                                     paramY= params.y;                                     break;                            caseMotionEvent.ACTION_MOVE:                                     intdx = (int) event.getRawX() - lastX;                                     intdy = (int) event.getRawY() - lastY;                                     params.x= paramX + dx;                                     params.y= paramY + dy;                                     //更新悬浮窗位置                                     wm.updateViewLayout(btn_floatView,params);                                     break;                            caseMotionEvent.ACTION_UP:                                     upX= (int) event.getRawX();                                     upY= (int) event.getRawY();                                     if(Math.abs(lastX-upX) < 5 && Math.abs(lastY-upY) < 5)                                     {                                               //单击后,弹出控制面板                                               createControlView();                                     }                                     break;                            }                            returntrue;                   }         });                       wm.addView(btn_floatView,params);}

控制窗口的显示代码如下:

/** * 创建控制面板 * 启动/暂停按钮、 最小化按钮、 退出按钮 */private void createControlView(){                         //设置Window flag         //params.flags&= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;         params.flags&= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 接受焦点输入         params.flags|= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; // 屏幕常亮         //wm.updateViewLayout(gView,params);                      //设置悬浮窗的长得宽         params.width= CONTROL_VIEW_WIDTH;         params.height= CONTROL_VIEW_HEIGHT;                 //params.x= 200;         //params.y= 400;                                    wm.removeView(btn_floatView);         wm.addView(controlViewGroup,params);}


 

 

三、如何模拟控制按键操作

有很多种方法,这里我采用的方法是直接往linux底层/dev/input/event*写事件,当然是需要手机root权限的。

先说下android界面捕获事件的流程吧:

用户点击-(硬件驱动部分)硬件产生一个中断,往/dev/input/event*写入一个相应的信号,android循环读取/dev/input/event*的事件,再分发给WindowManagerServer,最后再发到相应的ViewGroup和View。因此这里可以通过往/dev/input/event*写信号的方式,来达到模拟按键的目的。

类似分析可参见这里:

http://zuoshu.iteye.com/blog/1775606

http://blog.csdn.net/learnrose/article/details/6236890

总而言之,步骤如下:

1、查看设备属性

查看当前手机的输入设备:

>adb shell getevent


可以看到触摸屏name=mtk-tpd, 是event3,如果不放心,可以随意触摸屏幕,会发现收到很多event3的数据

2、确定按键命令格式信息

>adb shell getevent | grep event3


这个命令可以使得命令窗口只显示event3的数据信息,快速点击下屏幕发现有很多数据,如下图左侧部分:



但这些数据都是16进制数字,我们可没法看,别急,输入下面命令:

>adb shell getevent -d -l/dev/input/event3


通过给getevent加上-d -l参数来查看:

-d: show HID descriptor, if available

-l: label event types and names in plain text

这样就得到上图右边红色框里的数据了。

这样我们就得到了一次按键的数据命令格式如下:

EV_KEY BTN_TOUCH DOWN0001 014a 00000001 EV_ABS ABS_MT_TOUCH_MAJOR 00000001EV_ABS ABS_MT_POSITION_X 0000003eEV_ABS ABS_MT_POSITION_Y 000000efEV_ABS ABS_MT_TRACKING_ID 000000010003 0030 000000010003 0035 000000280003 0036 000001120003 0039 00000001 EV_SYN SYN_MT_REPORT 00000000EV_SYN SYN_REPORT 000000000000 0002 000000000000 0000 00000000 EV_ABS ABS_MT_TOUCH_MAJOR 00000001EV_ABS ABS_MT_POSITION_X 0000003eEV_ABS ABS_MT_POSITION_Y 000000efEV_ABS ABS_MT_TRACKING_ID 000000010003 0030 000000010003 0035 000000280003 0036 000001120003 0039 00000001 EV_SYN SYN_MT_REPORT 00000000EV_SYN SYN_REPORT 000000000000 0002 000000000000 0000 00000000 EV_KEY BTN_TOUCH UP0001 014a 00000000 EV_SYN SYN_MT_REPORT 00000000EV_SYN SYN_REPORT 000000000000 0002 000000000000 0000 00000000


根据手指的抖动情况,xy坐标可能会发送多次。

3、代码模拟按键

现在我们通过向event3发送数据来模拟按键。

这里,我们通过app操作手机的超级终端来发送命令。

代码如下:

 

// 命令定义 切记不要忘记最后的换行符public static final String CMD_DOWN =                            "sendevent/dev/input/event3 1 330 1 \n";public static final String CMD_TOUCH_MAJOR= "sendevent /dev/input/event3 3 4820 \n";public static final String CMD_X =                              "sendevent/dev/input/event3 3 53 ";public static final String CMD_Y =                              "sendevent/dev/input/event3 3 54 ";public static final String CMD_TRACK_ID =             "sendevent /dev/input/event3 357 0 \n";public static final StringCMD_SYN_MT_REPORT =        "sendevent/dev/input/event3 0 2 0 \n";public static final String CMD_SYN_REPORT =      "sendevent /dev/input/event3 00 0 \n";public static final String CMD_UP =                           "sendevent/dev/input/event3 1 330 0 \n"; Process proc;// 先获取root,运行shell//  因为su需要花一定时间,所以os放在最后才初始化try {                                  proc= Runtime.getRuntime().exec("su"); // 以root运行超级终端,设备需要拥有su权限          }catch (IOException e) {          e.printStackTrace(); }DataOutputStream os = newDataOutputStream(proc.getOutputStream()); // 模拟按键os.writeBytes(CMD_DOWN);os.flush();for (int i=0; i<1; i++){         os.writeBytes(CMD_TOUCH_MAJOR);         os.flush();         os.writeBytes(CMD_X+ x +" \n");         os.flush();         os.writeBytes(CMD_Y+ y +" \n");         os.flush();         os.writeBytes(CMD_TRACK_ID);         os.flush();         os.writeBytes(CMD_SYN_MT_REPORT);         os.flush();         os.writeBytes(CMD_SYN_REPORT);         os.flush();}os.writeBytes(CMD_UP);os.flush();os.writeBytes(CMD_SYN_MT_REPORT);os.flush();os.writeBytes(CMD_SYN_REPORT);os.flush();

实际可能还需要根据屏幕的坐标系,确定xy的方向。

我的手机是右上角为原点,自上向下为x正方向,自右向左为y正方向。

四、如何感知当前屏幕像素点颜色信息?

其实这有点类似于“Android屏幕截图”这个问题。

我们知道,DDMS可以很容易的获取Android 手机的屏幕截图,那么它是怎么做到的呢? 

其实,android手机上有一个叫做FrameBuffer的设备,图像信息都是通过FrameBuffer写到手机屏幕上去的。因此可以通过读取此设备中的数据来获取当前正在显示的图像。当然DDMS也是这么做到的。

FrameBuffer 对应的设备文件就是/dev/graphics/fb0。因此我们可以通过读取这个设备文件的内容来获取屏幕的图像数据。这个文件的信息如下:



使用这种方法第一个难题是获取FrameBuffer,因为默认的配置中FrameBuffer的读取权限是“root”,而Apk的权限最高只能提升到“system”,framework工作的权限也是“system”,所以需要手机root。

1、修改设备文件/dev/graphics/fb0的属性

为了能够读取内容,我们可以修改下fb0的设备权限为一般用户可读:

chmod 444 /dev/graphics/fb0


2、读取设备文件/dev/graphics/fb0内的数据

好了,接下来就是将fb0内的数据读入到我们自己的数组里:

 

// 获取屏幕大小:DisplayMetrics metrics = new DisplayMetrics();WindowManager WM = (WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);Display display = WM.getDefaultDisplay();display.getMetrics(metrics);int height = metrics.heightPixels; //屏幕高int width = metrics.widthPixels;     //屏幕的宽       // 获取显示方式int pixelformat = display.getPixelFormat(); // 1  RGBA_8888 实际读取到的fb0的数据格式为BGRAPixelFormat localPixelFormat1 = new PixelFormat();PixelFormat.getPixelFormatInfo(pixelformat,localPixelFormat1);int deepth = localPixelFormat1.bytesPerPixel;//位深 // 4   //         // 读取buffermyFrameBuffer = new byte[height * width * deepth * 2];// 双缓冲,实际我们也只需要用一个就够了 try {         os.writeBytes("chmod666 /dev/graphics/fb0 \n");         os.flush();          FileInputStreamstream = new  FileInputStream(newFile("/dev/graphics/fb0"));         myFrameBufferStream= new DataInputStream(stream);         myFrameBufferStream.readFully(myFrameBuffer);         myFrameBufferStream.close();         stream.close();        } catch (FileNotFoundException e) {         // TODOAuto-generated catch block         e.printStackTrace();} catch (IOException e) {         // TODOAuto-generated catch block         e.printStackTrace();}

 

接下来就是将读取到的数据显示在我的控件之上:

 

readFrameBuffer();                                     int w = 720;int h = 1280;Bitmap bitmap =Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);  for (int i=0; i

3、正确解析FrameBuffer内的数据

这里还需要强调一点,就是变量line_length,因为在adb shell里查看fb0的信息如下:

我们可以看到fb0的大小为7536640,我的手机是720*1280的,根据代码里的PixelFormat可以看到位深4,即一个像素4字节数据(BGRA),这样算下来应该是 720*1280*4=3686400,考虑双缓冲有3686400*2=7372800,咦,怎么比fb0的实际大小略小?我一直以为类似于bmp的数据格式,按行存储的,通过winhex查看fb0 cat出来的文件数据也没发现什么问题,但是显示的始终就是花屏,类似于这样:


折腾了好几天,度娘了好久,最后还是google到一个牛逼国外网友:

http://stackoverflow.com/questions/15468542/how-to-discover-framebuffer-width-on-android


The right padding is called"stride" (stride = (line_length in pixel) - width). Many device hadthis stride in the framebuffer if the display resolution is not multiply of 8.

So the formula is:

fileSize = line_length * yres * numberOfFrames

Don't multiply it with bpp/8, because the line_length is memory size (not pixelsize).

To retrive the line_length Youshould used FBIOGET_FSCREENINFO (0x4602 -17922) rather thanFBIOGET_VSCREENINFO (0x4600 -17922) like this:

>adb shell ioctl -rl 50 /dev/graphics/fb0 17922

My Galaxy Nexus return like this:

return buf: 6f 6d 61706662 00 00000000 00 00000000 00 00 a0 ac000000

01 00 00 00000000 00 00020000 00 01000100 00 00000080 0b 00000000


My Galaxy Nexus have line_length: 2944(0xb80).


意思就是说,内存里的一行字节数是line_length,而不是width*4!

查了好久,貌似基本都会提到ioctl,没想到强大的adb竟然也提供了这个命令,可以查看屏幕的参数信息:

>adbshell ioctl -rl 28 /dev/graphics/fb0 17920


屏幕宽度:0x000002d0 = 720

屏幕高度:0x00000500 = 1280

每像素bit:0x00000020 = 32

为了查看line_length,根据这位牛逼国外网友的提示:

>adbshell ioctl -rl 50 /dev/graphics/fb0 17922


也即我的line_length:0x00000b80 = 2944

这样,7536640 = 1280行 * 2944 * 2屏, 这回就可以正确解析FrameBuffer里的图像数据了。

 

五、   最终效果图

很粗糙,期待后续改进,现在是随机控制左右转向,要是能识别车辆就好了~(这个貌似单纯的通过像素点来判断恐怕不现实,不知是不是可以从游戏使用的引擎入手,不过那么高的高度也是后话了~~~)

 

 

 

更多相关文章

  1. SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
  2. 一句话锁定MySQL数据占用元凶
  3. Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实
  4. 一个3000万日活跃用户App的真实数据
  5. Android数据本地安全存储
  6. 仿写Android屏幕解锁小应用
  7. Android中 dip 和 px 的关系
  8. Android以当前Activity为基准进行截屏
  9. Android位置策略(五)

随机推荐

  1. Android(安卓)界面切换动画效果
  2. 移动开发新势力(下)
  3. Android应用内切换语言
  4. android开源项目二、
  5. android 常见面试题以及答案
  6. Android JNI开发详解(7)-线程操作,异常处
  7. android 电话状态的监听
  8. Android平板开发
  9. android编译系统学习
  10. Android面试系列文章2018之Android(安卓)