Android上实现微信跳一跳外挂

去年跳一跳很火,外挂也很火,当时自己也做了一个,跳个20来万分没问题。但是后来因为游戏一直更新,自己没时间去更新软件,而且之前的程序也没有写自动检测游戏结束和保存失败的情形的功能,所以现在基本上没法用了;最近游戏凉了,基本上不会再更新了,自己也是无聊,再彻底重新做一次;目前能跳30多万分吧

定个目标

要做的是一个扔在一边就可以自己跳的软件,只需一个测试手机,不需要任何其他的辅助工具就可以跳出高分,定个小目标就100万分吧

实现步骤

整个流程应该是先计算棋子(黑色的那个跳来跳去的就叫它棋子吧)和方块(下一个棋子落点的地方就叫它方块吧)中心点之间的距离,然后通过一个公式转换成需要点击的时间,然后自动点击屏幕进行跳跃

411F431097BBBD36973120B02903A57C.jpg

可行性分析

距离计算

移动悬浮窗

最简单的距离计算可以通过两个悬浮窗来实现,拖动悬浮窗到指定位置就可以计算出距离;这是可行的,但是效率低,需要手动操作,分数无上限,但是人累

//在service里面开启悬浮框代码
btnView = new ImageView(getApplicationContext());
        btnView.setImageResource(R.drawable.ic_star);
        windowManager = (WindowManager) getApplicationContext()
                .getSystemService(Context.WINDOW_SERVICE);
        params = new WindowManager.LayoutParams();
        // 设置Window Type
        params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
        // 设置悬浮框不可触摸
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_INSET_DECOR;
        // 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应
        params.format = PixelFormat.RGBA_8888;
        // 设置悬浮框的宽高
        params.width = 200;
        params.height = 200;
        params.gravity = Gravity.TOP;
        params.x = 300;
        params.y = 200;
        windowManager.addView(btnView, params);
图像识别

图像识别就需要先截取图像再利图像识别工具,android上最简单的使用openCV来做,简单点可以通过openCV的模板匹配来实现;这个方法需要很多模板,而且精度不高,识别时间较长,几秒钟吧,只是简单写写的话分数上限比较低(几百)

//模板匹配实现代码
Mat template = Highgui.imread(templateFilePath1, Highgui.CV_LOAD_IMAGE_COLOR);
Mat source = Highgui.imread(originalFilePath, Highgui.CV_LOAD_IMAGE_COLOR);
//创建于原图相同的大小,储存匹配度
Mat result = Mat.zeros(source.rows() - template.rows() + 1, source.cols() - template.cols() + 1, CvType.CV_8UC1);
//调用模板匹配方法
Imgproc.matchTemplate(source, template, result, Imgproc.TM_CCOEFF_NORMED);
//获得最可能点,MinMaxLocResult是其数据格式,包括了最大、最小点的位置x、y
Core.MinMaxLocResult mlr = Core.minMaxLoc(result);
System.out.println("相似度:" + mlr.maxVal);
Point matchLoc = mlr.maxLoc;
//在原图上的对应模板可能位置画一个绿色矩形
Core.rectangle(source, matchLoc, new Point(matchLoc.x + template.width(), matchLoc.y + template.height()), new Scalar(0, 255, 0));
        
颜色识别

颜色识别就是利用游戏色彩简单的特点,只需要识别颜色并找到规律就可以实现计算两点之间的距离;需要考虑的情况比较多,精度、时间都取决于写的算法;分数上限取决于算法和手机可运行时间

int color = bitmap.getPixel(x, y)

实现自动点击

自动点击,实现方法基本上就两种,第一是执行adb命令,可以连接电脑发送 adb命令实现,或者获取手机root权限,就可以本机实现了;一种是使用Accessbility辅助功能,但是其实它做不到触摸一段时间这个功能;

/**
     * @param cmd  需要执行的adb命令
     */
    public static void execShellCmd(String cmd) {
        try {
            if (process == null) {
                process = Runtime.getRuntime().exec("su");
                os = new DataOutputStream(process.getOutputStream());
            }
            os.writeBytes(cmd + "\n");
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

获取转换公式

转换公式可以先通过网上的粗略的公式来多次测试、调整、计算,然后得到一个比较精确的公式

double k = (distence * (-0.00020) + 1.495);
        if (k > 1.4165) {
            k = 1.4165;
        }
        time = (int) (k * distence);

具体实现

实现颜色识别➕获取root权限执行adb命令的方法

开启悬浮窗

第一步是在Service中开启悬浮窗,因为我们需要先开启悬浮窗才可以到游戏运行界面开启程序,需要先获取权限,获取悬浮窗权限还比较麻烦

快速集成动态权限申请:Android动态权限申请工具(包括悬浮窗)

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
//获取悬浮窗权限
FloatWindowManager.getInstance().applyOrShowFloatWindow(MainActivity.this)

截取屏幕

第一步是截取屏幕,有了root权限截图自然很简单,但是我运行的时候发现root命令的执行好像比较慢,而且先将截图保存到本地再读取出来比较慢一点,android5.0以后是自带截屏功能(全局)的,具体实现其实也不简单,需要开启录屏服务,然后去获取图片,类似开启相机预览,取出图片的感觉吧;

快速集成截屏:Android截屏、录屏工具

//开启录屏服务
ScreenRecordUtil.getInstance().screenShot(MainActivity.this, null);
//随时获取截图
Bitmap bitmap = ScreenRecordUtil.getInstance().getScreenShot();

颜色识别

颜色识别,其实就是看看两种颜色是不是一样或者相似,用LAB颜色空间算法,可以拿到两种颜色的差别有多大;

/**
     * LAB颜色空间计算色差,基于人眼对颜色的感知,
     * 可以表示人眼所能感受到的所有颜色。
     * L表示明度,A表示红绿色差,B表示蓝黄色差
     */
    public static int labAberration(int color1, int color2) {
        int r1 = Color.red(color1); // 取高两位
        int g1 = Color.green(color1);// 取中两位
        int b1 = Color.blue(color1);// 取低两位
        int r2 = Color.red(color2); // 取高两位
        int g2 = Color.green(color2);// 取中两位
        int b2 = Color.blue(color2);// 取低两位

        int rmean = (r1 + r2) / 2;
        int r = r1 - r2;
        int g = g1 - g2;
        int b = b1 - b2;
        return (int) Math.sqrt((2 + rmean / 256) * (Math.pow(r, 2)) + 4 * (Math.pow(g, 2)) + (2 + (255 - rmean) / 256) * (Math.pow(b, 2)));
    }

找到背景颜色

背景颜色是最重要的,因为大部分都是背景颜色,只要当前获取到的颜色和背景颜色不一致就可以知道应该是是进入方块了;可以从第一个点获取到背景颜色;可以注意到背景颜色基本上是一样的,稍微有一点点渐变,为了准确可以在每次获取颜色的时候更新背景颜色,使背景颜色更接近当前位置的背景;

找棋子位置

棋子位置是位于屏幕的下方的,所以为了节省时间只截取Bitmap的相应比例部分进行识别;棋子是永远不变的,从上面取几个特征颜色点;从下边遍历像素点的颜色,和对比特征颜色就可以找到棋子的起跳点位置

74E79A2856911FD47073BA62AD2560B2.jpg

清除棋子图像

棋子的长宽是不变的,找到棋子的位置后,我们将原bitmap上棋子所在的位置用背景颜色覆盖,这样可以避免之后识别的颜色干扰

获取方块的中心

假设第二张图片就是我们获取到的图片,我们要找到方块的中心就应该找到对角线,中心其实就是最左边的角和最右边的角的连线的中点;找到第一个顶点也很重要,这样可以知道方块的颜色,便于找其他点;而且当左右两个点有一个不准的时候,可以用顶点的X坐标和准的点的Y坐标来使用也可以;当两个点都不准的时候,直接使用顶点的X坐标和把Y坐标下移一点来使用也可以挽救一下

F516BE5284C7E3A2C27B5BDF12E03A04.png
找顶点

找顶点很简单,我们知道背景颜色,从上遍历下来只要颜色和背景颜色不一样就可以判定这是顶点了;

找第一个的点的时候可能会被一些元素影响,比如音符,这个比较好办,判断进入的这个颜色的长宽就可以排除,方块都是比较大的,影响元素都很小

方块是不是纯色

方块是不是纯色可以在找到顶点后取一个小范围来判定是不是纯色

找左右两边的点

这个有很多方法,第一种是继续遍历下去,每次进入方块点的X坐标会减小,每次出方块点的X坐标会增加,到最小和最大就是要找的两边的点;这种方法可行,但是中间会出现很多特殊情况;

这次选择另一种方法,方块左右两边和水平线的角度是固定的,斜率double k = 0.5773,利用角度和进入坐标可以生成一个直线的方程,按照这个方程的点往下找,当获取到和背景颜色一样或者和方块的颜色不一样的点就认为是左右两边要找的点了;

方块不是纯色

方块的颜色不是纯色的,不是纯色,第一个点还是能找准,找左右点的时候以颜色和背景颜色不一样为出去的点就好了,这样当左右两边(一般只会有一边)有方块的颜色连在一起的时候就会有一边的位置找不准,这时候根据两个点和顶点连线的距离判定,太远或者太近都舍弃;

0930E2C22F50CED040C345470C21FD52.jpg
特殊的纯色

当方块中只有一点线段或者中间是其他颜色的话,取一定的范围判断颜色是判断不出来不是纯色的,这时候可以采取尝试跳过不一样的颜色再次回到方块的颜色继续计算;

D58FC55A105E31AF2595E4D0185E3895.jpg

一些需要注意的地方

首先是方块形状,有的是圆形的,形状其实不影响上面的做法;

1CEF53BB6002BF95FD2D47333937FA8D.jpg

其实一般都会跳到中心,这时候中心是会出现一个白点的,在我们找到中心点的时候,如果点的颜色不是白色可以找找周围有没有白色,有的话再找到白点的中心,作为方块的中心

507F3D161DA9EF9F881400DCBF167887.jpg

遍历颜色没有必要一个一个像素遍历,可以先快速遍历,找到不同点以后再回过头精确的遍历

其实识别的时间很短,200ms以内,用adb执行点击需要的时间很长,可能就一秒左右吧,很慢了,然后跳一次后会产生一些波纹,需要等到波纹消失后才可以截图,这些都是需要等待的地方;

自我完善

在此基础上其实还会遇到很多特殊的情况,难复现,而且也不可能一直盯着手机;所以自动开始、保存失败时的截图、崩溃日志都是很重要的东西;

快速集成异常捕获:Android捕获Crash信息简单封装

CrashHander.getInstance().init(this, new ISaveErro() {
            @Override
            public void saveErroMsg(Throwable throwable) {
                
            }
        });

现在的版本大概每个小时可以跳2.4万,上次优化以后,只跳了一次就跳到24万分,但是崩溃只保存了日志和截图,没保留之崩溃前的几张有用的截图,所以原因没找到,这样优化下去感觉手机不卡可以跳到地老天荒;

G_0211045758.gif

项目地址:https://github.com/tyhjh/AlwaysJump

推荐阅读更多精彩内容

  • 其实也不能说算是外挂吧,算是个游戏小助手吧,毕竟不能抓包,也不能直接修改分数(据说之前可以直接抓包修改分数,不过这...
    zhuyuansj阅读 1,970评论 5 5
  • 一、梳理 跳一跳的游戏可以细分为两步骤:距离判断 + 按压模拟,这两步都有下面这些解决方案: 1、距离判断: ● ...
    TomasW阅读 1,111评论 0 1
  • 昨天微信上线了一款小程序游戏:跳一跳,昨天笔者也完了一下,发现这个看似简单的游戏想玩一个高分还是挺难的,要求比较精...
    EsonJack阅读 2,260评论 2 0
  • 【李聘的下周计划】 1、输出组21天挑战开始,每天30分钟阅读精华分享,每天一篇简书; 2、计划三次健身房(瑜伽课...
    李聘2022阅读 74评论 0 0
  • 这是第一篇东西,我也不知道会说到哪 看了一些文章,现在意识到觉得对于一个人来说,你学了什么,看了什么,都只能处于一...
    Garfield_L阅读 250评论 0 0
  • 沙溪。 7年来一直藏在我心中的一座秘密花园。 土地平旷,屋舍俨然,有良田美池桑竹之属。 是那种安静的美的让我都不忍...
    小靴子笔记阅读 127评论 0 0
  • TCP/IP 协议基本概念 OSI 模型所分的七层,在实际应用中,往往有一些层被整合,或者功能分散到其他层去。TC...
    浓情咖啡_4cba阅读 72评论 0 0