├── Android ├── build │ └── libs │ │ └── Android.jar └── src │ ├── com │ └── tencent │ │ └── wechatjump │ │ └── helper │ │ ├── Constants.java │ │ ├── Helper.java │ │ ├── Main.java │ │ ├── bean │ │ ├── DesType.java │ │ └── Pixel.java │ │ └── util │ │ ├── Color.java │ │ ├── DebugUtil.java │ │ ├── DesTypeChecker.java │ │ ├── FileUtil.java │ │ └── HelperUtil.java │ └── resource │ ├── adb_mac │ └── adb_windows ├── FAQ.md ├── README.md ├── Samples ├── 172_mark.png ├── 412_mark.png ├── 444_mark.png ├── 544_mark.png ├── find_vertex.png ├── k1.png ├── k2.png ├── piece_bottom.png ├── piece_top.png ├── rank.jpeg ├── target.png └── usb_debug.jpg ├── changelog.md └── release ├── 1.0.0 └── WechatJumpHelper-1.0.0.zip └── release.md /Android/build/libs/Android.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Android/build/libs/Android.jar -------------------------------------------------------------------------------- /Android/src/com/tencent/wechatjump/helper/Constants.java: -------------------------------------------------------------------------------- 1 | package com.tencent.wechatjump.helper; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Created by xushanmeng on 2017/12/30. 7 | */ 8 | 9 | public class Constants { 10 | 11 | /* 12 | * 距离和时间的换算系数 1600x2560机型推荐0.92 1440x2560机型推荐1.039 1080x1920机型推荐1.392 13 | * 720x1280机型推荐 2.078 14 | */ 15 | public static final double JUMP_PARAM_1600x2560 = 0.92; 16 | public static final double JUMP_PARAM_1440x2560 = 1.0392; 17 | public static final double JUMP_PARAM_1080x1920 = 1.39; 18 | public static final double JUMP_PARAM_720x1280 = 2.078; 19 | 20 | /* ************************************************************************************* 21 | * 22 | * 下面是一些数值是基于2560*1440分辨率的手机计算的位置,代码里会按比例换算,如果有偏差,请自行矫正 23 | * 24 | * ************************************************************************** 25 | * ********** 26 | */ 27 | // 基准屏幕分辨率 28 | public static final int BASE_SCREEN_WIDTH = 1440; 29 | public static final int BASE_SCREEN_HEIGHT = 2560; 30 | 31 | // 空白边缘可以不用检测,减少计算量 32 | public static final int TOP_BORDER = 400;// Top的值要在游戏得分的数字下面 33 | public static final int LEFT_BORDER = 10; 34 | public static final int RIGHT_BORDER = 10; 35 | public static final int BOTTOM_BORDER = 200; 36 | 37 | // 预置一些点用来计算斜率 38 | private static final float anchor1X = 1052; 39 | private static final float anchor1Y = 1132; 40 | private static final float anchor2X = 448; 41 | private static final float anchor2Y = 1481; 42 | private static final float anchor3X = 1407; 43 | private static final float anchor3Y = 1687; 44 | private static final float anchor4X = 564; 45 | private static final float anchor4Y = 1199; 46 | public static final float K1 = (anchor2Y - anchor1Y) 47 | / (anchor2X - anchor1X);// 往左上方跳的斜率 48 | public static final float K2 = (anchor4Y - anchor3Y) 49 | / (anchor4X - anchor3X);// 往右上方跳的斜率 50 | 51 | public static final int PIECE_BOTTOM_CENTER_SHIFT = 25;// 棋子底部椭圆中心到棋子最底部像素的偏移量 52 | public static final int PIECE_TOP_PIXELS = 6;// 棋子最顶部的最少像素个数 53 | public static final int PIECE_WIDTH_PIXELS = 100;// 棋子宽度,不同机型需要修改数值 54 | 55 | public static final int WHITE_POINT_MAX_HEIGHT = 31;// 落点中心靶点最大高度 56 | public static final int WHITE_POINT_MIM_HEIGHT = 29;// 落点中心靶点最小高度 57 | public static final int WHITE_POINT_MAX_WIDTH = 52;// 落点中心靶点最大宽度 58 | public static final int WHITE_POINT_MIM_WIDTH = 50;// 落点中心靶点最小宽度 59 | 60 | public static final int DES_MIN_SHIFT = 33;// 目标落点物体上顶点到中心的最小距离,如果计算的落点距离上顶点小于这个数值,将无法落稳 61 | 62 | // 模拟按压位置,放到重新开始的按钮上,可以自动重新开始 63 | public static final int RESTART_PRESS_X = 720; 64 | public static final int RESTART_PRESS_Y = 2116; 65 | 66 | // 缓存图片最大数量 67 | public static final int CACHE_FILE_MAX = 1000; 68 | 69 | public static final File BASE_DIR = new File("WechatJumpHelper"); 70 | public static final File ADB_PATH = new File(BASE_DIR, "adb"); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Android/src/com/tencent/wechatjump/helper/Helper.java: -------------------------------------------------------------------------------- 1 | package com.tencent.wechatjump.helper; 2 | 3 | import com.tencent.wechatjump.helper.bean.DesType; 4 | import com.tencent.wechatjump.helper.bean.Pixel; 5 | import com.tencent.wechatjump.helper.util.Color; 6 | import com.tencent.wechatjump.helper.util.DesTypeChecker; 7 | import com.tencent.wechatjump.helper.util.FileUtil; 8 | import com.tencent.wechatjump.helper.util.HelperUtil; 9 | 10 | import java.awt.Font; 11 | import java.awt.Graphics; 12 | import java.awt.image.BufferedImage; 13 | import java.io.File; 14 | 15 | import javax.imageio.ImageIO; 16 | 17 | import static com.tencent.wechatjump.helper.util.DebugUtil.*; 18 | 19 | /** 20 | * Created by xushanmeng on 2018/1/2. 21 | */ 22 | 23 | public class Helper { 24 | 25 | private double jumpParam; 26 | private StringBuilder debugInfo; 27 | private int cacheIndex = 0; 28 | private File cacheDir = new File(Constants.BASE_DIR, "imageCaches"); 29 | private File markDir = new File(Constants.BASE_DIR, "imageMarks"); 30 | 31 | public Helper(double jumpParam) { 32 | this.jumpParam = jumpParam; 33 | } 34 | 35 | public void start() { 36 | if (!DEBUG) { 37 | if (!cacheDir.exists()) { 38 | cacheDir.mkdirs(); 39 | } 40 | if (!markDir.exists()) { 41 | markDir.mkdirs(); 42 | } 43 | String[] filenames = cacheDir.list(); 44 | for (String filename : filenames) { 45 | String[] seg = filename.split("\\."); 46 | try { 47 | int index = Integer.valueOf(seg[0]); 48 | if (index > cacheIndex) { 49 | cacheIndex = index; 50 | } 51 | } catch (Exception e) { 52 | continue; 53 | } 54 | } 55 | } 56 | while (true) { 57 | try { 58 | debugInfo = new StringBuilder(); 59 | long startTime = System.currentTimeMillis(); 60 | long screencapTime = 0; 61 | long downloadTime = 0; 62 | long calculateTime = 0; 63 | long cacheTime = 0; 64 | long exeJumpTime = 0; 65 | long recacheTime = 0; 66 | long tempTime = startTime; 67 | File cacheFile; 68 | if (!DEBUG) { 69 | // 获取截图 70 | boolean result = HelperUtil.execute(Constants.ADB_PATH.getAbsolutePath(), "shell", "screencap", "-p", "/sdcard/screen.png"); 71 | if (!result) { 72 | System.out.println("截屏失败"); 73 | Thread.sleep(5000); 74 | continue; 75 | } else { 76 | screencapTime = System.currentTimeMillis() - tempTime; 77 | } 78 | tempTime += screencapTime; 79 | result = HelperUtil.execute(Constants.ADB_PATH.getAbsolutePath(), "pull", "/sdcard/screen.png", Constants.BASE_DIR.getAbsolutePath()); 80 | if (!result) { 81 | System.out.println("下载截屏失败"); 82 | Thread.sleep(5000); 83 | continue; 84 | } else { 85 | downloadTime = System.currentTimeMillis() - tempTime; 86 | } 87 | tempTime += downloadTime; 88 | cacheFile = new File(cacheDir, (++cacheIndex) + ".png"); 89 | if (cacheIndex > Constants.CACHE_FILE_MAX) { 90 | // 最多缓存1000张图片 91 | File oldFile = new File(cacheDir, (cacheIndex - Constants.CACHE_FILE_MAX) + ".png"); 92 | oldFile.delete(); 93 | } 94 | // 保存缓存图片 95 | File imageFile = new File(Constants.BASE_DIR, "screen.png"); 96 | result = FileUtil.copyFile(imageFile, cacheFile); 97 | imageFile.deleteOnExit(); 98 | if (!result) { 99 | System.out.println(cacheIndex + ". 截屏缓存保存失败"); 100 | } 101 | cacheTime = System.currentTimeMillis() - tempTime; 102 | tempTime += cacheTime; 103 | } else { 104 | cacheIndex = DEBUG_IMAGE; 105 | cacheFile = new File(cacheDir, DEBUG_IMAGE + ".png"); 106 | tempTime = System.currentTimeMillis(); 107 | } 108 | 109 | // 读取图片像素 110 | BufferedImage image = ImageIO.read(cacheFile); 111 | final int screenWidth = image.getWidth(); 112 | final int screenHeight = image.getHeight(); 113 | debugInfo.append("屏幕参数 ").append(screenWidth).append("x") 114 | .append(screenHeight).append("\n"); 115 | if (screenWidth == 0 || screenHeight == 0) { 116 | System.out.println(cacheIndex + ". 截屏图片读取错误"); 117 | Thread.sleep(5000); 118 | continue; 119 | } 120 | Pixel[][] pixels = new Pixel[screenHeight][screenWidth]; 121 | for (int i = 0; i < screenHeight; i++) { 122 | for (int j = 0; j < screenWidth; j++) { 123 | pixels[i][j] = new Pixel(); 124 | pixels[i][j].color = image.getRGB(j, i); 125 | pixels[i][j].x = j; 126 | pixels[i][j].y = i; 127 | 128 | } 129 | } 130 | 131 | final int TOP_BORDER = HelperUtil.transH(screenHeight, HelperUtil.adjustTopBorder(screenWidth,screenHeight)); 132 | final int BOTTOM_BORDER = HelperUtil.transH(screenHeight, HelperUtil.adjustBottomBorder(screenWidth,screenHeight)); 133 | final int LEFT_BORDER = HelperUtil.transW(screenWidth, Constants.LEFT_BORDER); 134 | final int RIGHT_BORDER = HelperUtil.transW(screenWidth, Constants.RIGHT_BORDER); 135 | 136 | final int PIECE_WIDTH_PIXELS = HelperUtil.transW(screenWidth, Constants.PIECE_WIDTH_PIXELS); 137 | final int PIECE_TOP_PIXELS = HelperUtil.transW(screenWidth, Constants.PIECE_TOP_PIXELS); 138 | final int PIECE_BOTTOM_CENTER_SHIFT = HelperUtil.transW(screenWidth, Constants.PIECE_BOTTOM_CENTER_SHIFT); 139 | 140 | final int DES_MIN_SHIFT = HelperUtil.transW(screenWidth, Constants.DES_MIN_SHIFT); 141 | 142 | /* 计算棋子位置 */ 143 | Pixel piece = new Pixel(); 144 | for (int i = TOP_BORDER; i < screenHeight - BOTTOM_BORDER; i++) { 145 | int startX = 0; 146 | int endX = 0; 147 | for (int j = LEFT_BORDER; j < screenWidth - RIGHT_BORDER; j++) { 148 | int red = Color.red(pixels[i][j].color); 149 | int green = Color.green(pixels[i][j].color); 150 | int blue = Color.blue(pixels[i][j].color); 151 | if (50 < red && red < 55 && 50 < green && green < 55 152 | && 55 < blue && blue < 65) {// 棋子顶部颜色 153 | // 如果侦测到棋子相似颜色,记录下开始点 154 | if (startX == 0) { 155 | startX = j; 156 | endX = 0; 157 | } 158 | } else if (endX == 0) { 159 | // 记录下结束点 160 | endX = j; 161 | 162 | if (endX - startX < PIECE_TOP_PIXELS) { 163 | // 规避井盖的BUG,像素点不够长,则重新计算 164 | startX = 0; 165 | endX = 0; 166 | } 167 | } 168 | if (50 < red && red < 60 && 55 < green && green < 65 169 | && 95 < blue && blue < 105) {// 棋子底部的颜色 170 | // 最后探测到的颜色就是棋子的底部像素 171 | piece.y = i; 172 | } 173 | } 174 | if (startX != 0 && piece.x == 0) { 175 | piece.x = (startX + endX) / 2; 176 | } 177 | } 178 | if (piece.x == 0 || piece.y == 0) { 179 | System.out.println(cacheIndex + ". 未找到棋子坐标"); 180 | final int PRESS_Y = HelperUtil.transH(screenHeight, Constants.RESTART_PRESS_Y); 181 | final int PRESS_X = HelperUtil.transW(screenWidth, Constants.RESTART_PRESS_X); 182 | HelperUtil.execute(Constants.ADB_PATH.getAbsolutePath(), "shell", "input", "swipe", PRESS_X + "", PRESS_Y + "", PRESS_X + "", PRESS_Y + "", 100 + ""); 183 | Thread.sleep(5000); 184 | continue; 185 | } 186 | 187 | // 棋子纵坐标从底部边缘调整到底部中心 188 | piece.y -= PIECE_BOTTOM_CENTER_SHIFT; 189 | 190 | System.out.print(cacheIndex + ". 棋子坐标点[" + piece.x + ", " + piece.y + "], "); 191 | 192 | // 计算目标位置 193 | Pixel des = new Pixel(); 194 | 195 | Pixel firstPixcel = null;// 目标落点最顶部首个像素位置 196 | 197 | // 双重循环寻找落点第一个像素 198 | for (int i = TOP_BORDER; i < screenHeight - BOTTOM_BORDER; i++) { 199 | for (int j = LEFT_BORDER; j < screenWidth - RIGHT_BORDER; j++) { 200 | if (piece.x - PIECE_WIDTH_PIXELS / 2 < j && j < piece.x + PIECE_WIDTH_PIXELS / 2) { 201 | // 忽略和棋子处于同一垂直线的这些像素 202 | continue; 203 | } 204 | if (!Color.compareColor(pixels[i][j].color, pixels[i][0].color, 12)) { 205 | // 发现色差,记录下落点顶部第一个像素 206 | firstPixcel = pixels[i][j]; 207 | break; 208 | } 209 | } 210 | if (firstPixcel != null) { 211 | break; 212 | } 213 | } 214 | if (firstPixcel == null) { 215 | System.out.println(cacheIndex + ". 未找到目标落点坐标"); 216 | Thread.sleep(5000); 217 | continue; 218 | } 219 | debugInfo.append("落点首个像素 firstPixel").append(firstPixcel) 220 | .append("\n"); 221 | 222 | // 计算目标点类型 223 | 224 | Pixel whitePointCenter = findWhitePointCenter(pixels, firstPixcel); 225 | Pixel puerCenter = findPuerCenter(pixels, firstPixcel); 226 | 227 | DesType desType = DesTypeChecker.getDesType(pixels, firstPixcel, puerCenter); 228 | 229 | String hasWhitePoint; 230 | if (whitePointCenter != null) { 231 | // 如果有白点,那目标落点为白点中心 232 | des = whitePointCenter; 233 | hasWhitePoint = "有靶点"; 234 | } else { 235 | hasWhitePoint = "无靶点"; 236 | if (puerCenter != null) { 237 | des = puerCenter; 238 | } else { 239 | // 如果是其他类型且没有靶点,通过斜率计算落点 240 | int count = 0; 241 | for (int i = firstPixcel.x; i < screenWidth - RIGHT_BORDER; i++) { 242 | if (Color.compareColor(pixels[firstPixcel.y][i].color, pixels[firstPixcel.y][firstPixcel.x].color, 10)) { 243 | ++count; 244 | } else { 245 | break; 246 | } 247 | } 248 | 249 | des.x = firstPixcel.x + count / 2; 250 | float k;// 斜率 251 | if ((firstPixcel.y - piece.y) * 1.0f / (firstPixcel.x - piece.x) < 0) { 252 | k = Constants.K1; 253 | } else { 254 | k = Constants.K2; 255 | } 256 | des.y = Math.round(k * (des.x - piece.x) + piece.y); 257 | if (des.y < firstPixcel.y + DES_MIN_SHIFT) { 258 | des.y = firstPixcel.y + DES_MIN_SHIFT; 259 | } 260 | } 261 | } 262 | 263 | /* 计算距离 */ 264 | double distance = HelperUtil.calculateDistance(piece, des); 265 | System.out.print("目标落点[" + des.x + ", " + des.y + "], " + desType.getName() + ", " + hasWhitePoint + ", 距离" + Math.round(distance) + "px, "); 266 | calculateTime = System.currentTimeMillis() - tempTime; 267 | tempTime += calculateTime; 268 | if (!DEBUG) { 269 | // 执行跳跃 270 | long pressTime = (long) (distance * jumpParam); 271 | System.out.println("模拟按压" + pressTime + "ms."); 272 | final int PRESS_Y = HelperUtil.transH(screenHeight, screenHeight / 2 - 250 + (int) (Math.random() * 500)); 273 | final int PRESS_X = HelperUtil.transW(screenWidth, screenWidth / 2 - 150 + (int) (Math.random() * 300)); 274 | HelperUtil.execute(Constants.ADB_PATH.getAbsolutePath(), "shell", "input", "swipe", PRESS_X + "", PRESS_Y + "", PRESS_X + "", PRESS_Y + "", pressTime + ""); 275 | exeJumpTime = System.currentTimeMillis() - tempTime; 276 | tempTime += exeJumpTime; 277 | // 保存带标记的图像 278 | Graphics graphics = image.getGraphics(); 279 | graphics.setColor(java.awt.Color.RED); 280 | graphics.drawLine(des.x, des.y, piece.x, piece.y); 281 | graphics.setFont(new Font("宋体", Font.BOLD, HelperUtil.transW(screenWidth, 60))); 282 | graphics.drawString(desType.getName() + "," + hasWhitePoint, HelperUtil.transW(screenWidth, 10), HelperUtil.transW(screenWidth, 70)); 283 | graphics.drawString("跳跃距离" + Math.round(distance) + "px", HelperUtil.transW(screenWidth, 10), HelperUtil.transW(screenWidth, 135)); 284 | graphics.drawString("模拟按压" + pressTime + "ms", HelperUtil.transW(screenWidth, 10), HelperUtil.transW(screenWidth, 200)); 285 | ImageIO.write(image, "png", new File(markDir, cacheIndex + "_mark.png")); 286 | recacheTime = System.currentTimeMillis() - tempTime; 287 | } else { 288 | System.out.println("DEBUG"); 289 | System.out.print(debugInfo); 290 | } 291 | 292 | System.out.print("截图耗时" + screencapTime + "ms, "); 293 | System.out.print("下载耗时" + downloadTime + "ms, "); 294 | System.out.print("缓存耗时" + cacheTime + "ms, "); 295 | System.out.print("计算耗时" + calculateTime + "ms, "); 296 | System.out.print("跳跃耗时" + exeJumpTime + "ms, "); 297 | System.out.print("标记耗时" + recacheTime + "ms, "); 298 | System.out.println("总耗时" 299 | + (System.currentTimeMillis() - startTime) + "ms\n"); 300 | 301 | /* 停留一会,保证棋子落稳 */ 302 | if (desType == DesType.COVER || desType == DesType.CUBE 303 | || desType == DesType.SHOP || desType == DesType.DISC) { 304 | // 特殊类型的落点,停留时间长了会加分 305 | Thread.sleep(2000); 306 | } else { 307 | Thread.sleep(1500); 308 | } 309 | } catch (Exception e) { 310 | e.printStackTrace(); 311 | } 312 | } 313 | } 314 | 315 | /** 316 | * 获取纯平像素点中心,如果检测到不是纯平面,则返回null 317 | */ 318 | public Pixel findPuerCenter(Pixel[][] pixels, Pixel firstPixcel) { 319 | 320 | Pixel[] vertexs = HelperUtil.findVertexs(pixels, firstPixcel); 321 | Pixel puerLeft = vertexs[0]; 322 | Pixel puerTop = vertexs[1]; 323 | Pixel puerRight = vertexs[2]; 324 | Pixel puerBottom = vertexs[3]; 325 | Pixel puerCenter; 326 | 327 | debugInfo.append("PuerBorder[") 328 | .append(puerLeft.x).append(", ") 329 | .append(puerTop.y).append(", ") 330 | .append(puerRight.x).append(", ") 331 | .append(puerBottom.y).append("]\n"); 332 | int screenWidth = pixels[0].length; 333 | int screenHeight = pixels.length; 334 | int puerWidth = puerRight.x - puerLeft.x + 1; 335 | int puerHeight = puerBottom.y - puerTop.y + 1; 336 | int distanceMin = HelperUtil.transW(screenWidth, 30); 337 | if (HelperUtil.transW(screenWidth, 110) <= puerWidth 338 | && puerWidth <= HelperUtil.transW(screenWidth, 610) 339 | && HelperUtil.transW(screenWidth, 64) <= puerHeight 340 | && puerHeight <= HelperUtil.transW(screenWidth, 350) 341 | && HelperUtil.calculateDistance(puerTop, puerLeft) > distanceMin 342 | && HelperUtil.calculateDistance(puerTop, puerRight) > distanceMin 343 | && HelperUtil.calculateDistance(puerBottom, puerLeft) > distanceMin 344 | && HelperUtil.calculateDistance(puerBottom, puerRight) > distanceMin) { 345 | puerCenter = pixels[(puerTop.y + puerBottom.y) / 2][(puerLeft.x + puerRight.x) / 2]; 346 | } else { 347 | puerCenter = null; 348 | } 349 | return puerCenter; 350 | } 351 | 352 | /** 353 | * 获取靶点(落点中心的白点)中心,如果检测到没有靶点,则返回null 354 | */ 355 | private Pixel findWhitePointCenter(Pixel[][] pixels, Pixel firstPixcel) { 356 | int screenWidth = pixels[0].length; 357 | int screenHeight = pixels.length; 358 | // 尝试寻找白点 359 | Pixel whitePointBase = null; 360 | final int WHITE_POINT_COLOR = 0xf5f5f5; 361 | int widthShift = HelperUtil.transW(screenWidth, 15); 362 | int heightShift = HelperUtil.transW(screenWidth, 200); 363 | for (int i = firstPixcel.y; i < firstPixcel.y + heightShift; i++) { 364 | for (int j = firstPixcel.x - widthShift; j < firstPixcel.x + widthShift; j++) { 365 | if (Color.compareColor(pixels[i][j].color, WHITE_POINT_COLOR)) { 366 | // 发现白点相似颜色 367 | whitePointBase = pixels[i][j]; 368 | break; 369 | } 370 | } 371 | if (whitePointBase != null) { 372 | break; 373 | } 374 | } 375 | if (whitePointBase == null) { 376 | return null; 377 | } 378 | if (DesTypeChecker.checkBumf(pixels, firstPixcel)) { 379 | // 如果是卫生纸,修正白色点的基准 380 | whitePointBase = pixels[firstPixcel.y + HelperUtil.transW(screenWidth, 62)][firstPixcel.x]; 381 | } 382 | 383 | Pixel[] vertexs = HelperUtil.findVertexs(pixels, whitePointBase); 384 | Pixel whitePointLeft = vertexs[0]; 385 | Pixel whitePointTop = vertexs[1]; 386 | Pixel whitePointRight = vertexs[2]; 387 | Pixel whitePointBottom = vertexs[3]; 388 | Pixel whitePointCenter; 389 | 390 | debugInfo.append("WhitePointBorder[").append(whitePointLeft.x) 391 | .append(", ").append(whitePointTop.y).append(", ") 392 | .append(whitePointRight.x).append(", ") 393 | .append(whitePointBottom.y).append("]\n"); 394 | int whitePointWidth = whitePointRight.x - whitePointLeft.x + 1; 395 | int whitePointHeight = whitePointBottom.y - whitePointTop.y + 1; 396 | if (HelperUtil.transW(screenWidth, Constants.WHITE_POINT_MIM_WIDTH) <= whitePointWidth 397 | && whitePointWidth <= HelperUtil.transW(screenWidth, Constants.WHITE_POINT_MAX_WIDTH) 398 | && HelperUtil.transW(screenWidth, Constants.WHITE_POINT_MIM_HEIGHT) <= whitePointHeight 399 | && whitePointHeight <= HelperUtil.transW(screenWidth, Constants.WHITE_POINT_MAX_HEIGHT)) { 400 | whitePointCenter = pixels[(whitePointTop.y + whitePointBottom.y) / 2][(whitePointLeft.x + whitePointRight.x) / 2]; 401 | } else { 402 | whitePointCenter = null; 403 | } 404 | return whitePointCenter; 405 | } 406 | 407 | } 408 | -------------------------------------------------------------------------------- /Android/src/com/tencent/wechatjump/helper/Main.java: -------------------------------------------------------------------------------- 1 | package com.tencent.wechatjump.helper; 2 | 3 | import com.tencent.wechatjump.helper.util.DebugUtil; 4 | import com.tencent.wechatjump.helper.util.FileUtil; 5 | import com.tencent.wechatjump.helper.util.HelperUtil; 6 | 7 | import java.awt.image.BufferedImage; 8 | import java.io.BufferedReader; 9 | import java.io.File; 10 | import java.io.FileOutputStream; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.util.ArrayList; 14 | import java.util.Scanner; 15 | 16 | import javax.imageio.ImageIO; 17 | 18 | /** 19 | * Created by xushanmeng on 2017/12/30. 20 | */ 21 | 22 | public class Main { 23 | 24 | public static void main(String[] args) { 25 | if (DebugUtil.DEBUG) { 26 | new Helper(1).start(); 27 | return; 28 | } 29 | if (!Constants.BASE_DIR.exists()) { 30 | Constants.BASE_DIR.mkdirs(); 31 | } 32 | if (!Constants.ADB_PATH.exists()) { 33 | try { 34 | String adb = null; 35 | String system = System.getProperty("os.name"); 36 | if (system.contains("Windows")) { 37 | adb = "resource/adb_windows"; 38 | } else if (system.contains("Mac")) { 39 | adb = "resource/adb_mac"; 40 | } else { 41 | System.out.println("该程序暂不支持" + system); 42 | return; 43 | } 44 | 45 | InputStream is = Main.class.getClassLoader().getResourceAsStream(adb); 46 | boolean result = FileUtil.copy(is, new FileOutputStream(Constants.ADB_PATH)); 47 | if (!result) { 48 | System.out.println("adb解压失败"); 49 | return; 50 | } 51 | Constants.ADB_PATH.setExecutable(true); 52 | } catch (Exception e) { 53 | System.out.println("adb解压失败"); 54 | return; 55 | } 56 | } 57 | ArrayList devices = getConnectedDevices(); 58 | if (devices.size() == 1) { 59 | System.out.println("检测到您连接的手机为:" + devices.get(0) + "\n"); 60 | } else { 61 | if (devices.size() == 0) { 62 | System.out.println("未检测到已连接的手机,等待手机连接..."); 63 | } else { 64 | System.out.println("检测到已连接多部手机,请移除多余手机,只留下一台..."); 65 | } 66 | while (true) { 67 | try { 68 | Thread.sleep(1000); 69 | } catch (InterruptedException e) { 70 | e.printStackTrace(); 71 | return; 72 | } 73 | devices = getConnectedDevices(); 74 | if (devices.size() == 1) { 75 | break; 76 | } 77 | } 78 | System.out.println("\n检测到您连接的手机为:" + devices.get(0) + "\n"); 79 | } 80 | System.out.println("目前已适配手机分辨率\n" 81 | + " a) 1600x2560,跳跃系数0.92\n" 82 | + " b) 1440x2560,跳跃系数1.039\n" 83 | + " c) 1080x1920,跳跃系数1.392\n" 84 | + " d) 720x1280,跳跃系数2.078\n"); 85 | System.out.println("正在检测手机分辨率,请稍候..."); 86 | File imageFile = new File(Constants.BASE_DIR, "screen.png"); 87 | if (imageFile.exists()) { 88 | imageFile.delete(); 89 | } 90 | boolean result = HelperUtil.execute(Constants.ADB_PATH.getAbsolutePath(), "shell", "screencap", "-p", "/sdcard/screen.png"); 91 | if (!result) { 92 | System.out.println("手机分辨率检测失败,请检查电脑与手机连接和手机设置。"); 93 | return; 94 | } 95 | result = HelperUtil.execute(Constants.ADB_PATH.getAbsolutePath(), "pull", "/sdcard/screen.png", Constants.BASE_DIR.getAbsolutePath()); 96 | if (!result) { 97 | System.out.println("手机分辨率检测失败,请检查电脑与手机连接和手机设置。"); 98 | return; 99 | } 100 | int screenWidth; 101 | int screenHeight; 102 | try { 103 | BufferedImage image = ImageIO.read(imageFile); 104 | screenWidth = image.getWidth(); 105 | screenHeight = image.getHeight(); 106 | } catch (Exception e) { 107 | e.printStackTrace(); 108 | System.out.println("手机分辨率检测失败,请检查电脑与手机连接和手机设置。"); 109 | return; 110 | } 111 | if (screenWidth == 0 || screenHeight == 0) { 112 | System.out.println("手机分辨率检测失败,请检查电脑与手机连接和手机设置。"); 113 | return; 114 | } 115 | System.out.println("检测到您的手机分辨率为" + screenWidth + "x" + screenHeight); 116 | String screenStr = screenWidth + "x" + screenHeight; 117 | double jumpParam = 0; 118 | switch (screenStr) { 119 | case "1600x2560": 120 | jumpParam = Constants.JUMP_PARAM_1600x2560; 121 | break; 122 | case "1440x2560": 123 | jumpParam = Constants.JUMP_PARAM_1440x2560; 124 | break; 125 | case "1080x1920": 126 | jumpParam = Constants.JUMP_PARAM_1080x1920; 127 | break; 128 | case "720x1280": 129 | jumpParam = Constants.JUMP_PARAM_720x1280; 130 | break; 131 | } 132 | Scanner scanner = new Scanner(System.in); 133 | if (jumpParam == 0) { 134 | System.out.print("暂时不兼容您的手机分辨率,请直接输入您的跳跃系数:"); 135 | } else { 136 | System.out.print("推荐跳跃系数为" + jumpParam + ",是否使用系统推荐跳跃系数?(y/n)"); 137 | while (true) { 138 | String line = scanner.nextLine(); 139 | if ("y".equalsIgnoreCase(line.trim()) || "yes".equalsIgnoreCase(line.trim())) { 140 | break; 141 | } else if ("n".equalsIgnoreCase(line.trim()) || "no".equalsIgnoreCase(line.trim())) { 142 | jumpParam = 0; 143 | System.out.print("请输入您的跳跃系数:"); 144 | break; 145 | } else { 146 | System.out.print("命令输入错误,请重新输入:"); 147 | } 148 | } 149 | } 150 | while (jumpParam == 0) { 151 | String line = scanner.nextLine(); 152 | if (line == null) { 153 | continue; 154 | } 155 | try { 156 | jumpParam = Double.valueOf(line); 157 | } catch (Exception e) { 158 | } 159 | if (jumpParam == 0) { 160 | System.out.print("输入格式错误,请重新输入:"); 161 | } 162 | } 163 | System.out.println("正在开始自动跳一跳..."); 164 | new Helper(jumpParam).start(); 165 | } 166 | 167 | private static ArrayList getConnectedDevices() { 168 | ArrayList devices = new ArrayList<>(); 169 | try { 170 | Process process = Runtime.getRuntime().exec(new String[]{Constants.ADB_PATH.getAbsolutePath(), "devices"}); 171 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 172 | String line; 173 | boolean startCount = false; 174 | String deviceName; 175 | while ((line = reader.readLine()) != null) { 176 | if (line.contains("List of devices attached")) { 177 | startCount = true; 178 | } else if (startCount && !line.trim().isEmpty() 179 | && !line.contains("unauthorized") 180 | && !line.contains("offline") 181 | && !line.contains("* daemon")) { 182 | String seg[] = line.split("\t"); 183 | deviceName = seg[0]; 184 | devices.add(deviceName); 185 | } 186 | } 187 | } catch (Exception e) { 188 | } 189 | return devices; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Android/src/com/tencent/wechatjump/helper/bean/DesType.java: -------------------------------------------------------------------------------- 1 | package com.tencent.wechatjump.helper.bean; 2 | 3 | /** 4 | * 目标落点的类型 5 | */ 6 | 7 | public enum DesType { 8 | 9 | CUBE("魔方"), 10 | COVER("井盖"), 11 | SHOP("商店"), 12 | DISC("唱片"), 13 | PUER("平面物体"), 14 | BUMF("卫生纸"), 15 | OTHER("其他物体"); 16 | 17 | private String name; 18 | 19 | DesType(String name) { 20 | this.name = name; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Android/src/com/tencent/wechatjump/helper/bean/Pixel.java: -------------------------------------------------------------------------------- 1 | package com.tencent.wechatjump.helper.bean; 2 | 3 | import com.tencent.wechatjump.helper.util.Color; 4 | 5 | /** 6 | * 像素点 7 | */ 8 | 9 | public class Pixel { 10 | public int x; 11 | public int y; 12 | public int color; 13 | 14 | @Override 15 | public String toString() { 16 | return "[" + x + ", " + y + "]-(" + Color.red(color) + ", " + Color.green(color) + ", " + Color.blue(color) + ")"; 17 | } 18 | 19 | @Override 20 | public boolean equals(Object o) { 21 | if (this == o) { 22 | return true; 23 | } 24 | if (o instanceof Pixel) { 25 | Pixel op = (Pixel) o; 26 | if (this.x == op.x 27 | && this.y == op.y 28 | && this.color == op.color) { 29 | return true; 30 | } 31 | } 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Android/src/com/tencent/wechatjump/helper/util/Color.java: -------------------------------------------------------------------------------- 1 | package com.tencent.wechatjump.helper.util; 2 | 3 | import com.tencent.wechatjump.helper.bean.Pixel; 4 | 5 | /** 6 | * Created by xushanmeng on 2017/12/30. 7 | */ 8 | 9 | public class Color { 10 | 11 | /** 12 | * 提取红色值,范围0-255 13 | */ 14 | public static int red(int color) { 15 | return (color >> 16) & 0xFF; 16 | } 17 | 18 | /** 19 | * 提取绿色值,范围0-255 20 | */ 21 | public static int green(int color) { 22 | return (color >> 8) & 0xFF; 23 | } 24 | 25 | /** 26 | * 提取蓝色值,范围0-255 27 | */ 28 | public static int blue(int color) { 29 | return color & 0xFF; 30 | } 31 | 32 | /** 33 | * 比较颜色是否相近 34 | * @param shift 容差 35 | */ 36 | public static boolean compareColor(int color1, int color2, int shift) { 37 | int red1 = Color.red(color1); 38 | int green1 = Color.green(color1); 39 | int blue1 = Color.blue(color1); 40 | int red2 = Color.red(color2); 41 | int green2 = Color.green(color2); 42 | int blue2 = Color.blue(color2); 43 | if (Math.abs(red1 - red2) + Math.abs(green1 - green2) + Math.abs(blue1 - blue2) > shift) { 44 | return false; 45 | } 46 | return true; 47 | } 48 | 49 | /** 50 | * 比较颜色是否相同 51 | */ 52 | public static boolean compareColor(int color1, int color2) { 53 | return compareColor(color1, color2, 0); 54 | } 55 | 56 | /** 57 | * 比较颜色是否相同 58 | */ 59 | public static boolean compareColor(Pixel pixel1, Pixel pixel2) { 60 | return compareColor(pixel1.color, pixel2.color); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Android/src/com/tencent/wechatjump/helper/util/DebugUtil.java: -------------------------------------------------------------------------------- 1 | package com.tencent.wechatjump.helper.util; 2 | 3 | /** 4 | * Created by xushanmeng on 2018/1/1. 5 | */ 6 | 7 | public class DebugUtil { 8 | /** 9 | * 如果开启调试,则会针对某一张缓存图片进行数据打印 10 | */ 11 | public final static boolean DEBUG = false; 12 | /** 13 | * 调试缓存图片的序号 14 | */ 15 | public final static int DEBUG_IMAGE = 10010; 16 | } 17 | -------------------------------------------------------------------------------- /Android/src/com/tencent/wechatjump/helper/util/DesTypeChecker.java: -------------------------------------------------------------------------------- 1 | package com.tencent.wechatjump.helper.util; 2 | 3 | import com.tencent.wechatjump.helper.bean.DesType; 4 | import com.tencent.wechatjump.helper.bean.Pixel; 5 | 6 | /** 7 | * Created by xushanmeng on 2018/1/1. 8 | */ 9 | 10 | public class DesTypeChecker { 11 | 12 | /** 13 | * 获取目标落点类型 14 | * 15 | * @param pixels 像素矩阵 16 | * @param firstPixcel 探测到的落点第一个像素 17 | * @param puerCenter 目标落点纯平面的中心,如果没有传null 18 | */ 19 | public static final DesType getDesType(Pixel[][] pixels, Pixel firstPixcel, Pixel puerCenter) { 20 | DesType desType = DesType.OTHER; 21 | if (DesTypeChecker.checkCube(pixels, firstPixcel)) { 22 | desType = DesType.CUBE; 23 | } else if (DesTypeChecker.checkCover(pixels, firstPixcel)) { 24 | desType = DesType.COVER; 25 | } else if (DesTypeChecker.checkShop(pixels, firstPixcel)) { 26 | desType = DesType.SHOP; 27 | } else if (DesTypeChecker.checkDisc(pixels, firstPixcel)) { 28 | desType = DesType.DISC; 29 | } else if (DesTypeChecker.checkBumf(pixels, firstPixcel)) { 30 | desType = DesType.BUMF; 31 | } else if (puerCenter != null) { 32 | desType = DesType.PUER; 33 | } 34 | return desType; 35 | } 36 | 37 | /** 38 | * 检查是否是魔方 39 | */ 40 | public static final boolean checkCube(Pixel[][] pixels, Pixel firstPixcel) { 41 | int width = pixels[0].length; 42 | int height = pixels.length; 43 | 44 | int x1 = firstPixcel.x; 45 | int y1 = firstPixcel.y + HelperUtil.transW(width, 30); 46 | int x2 = firstPixcel.x + HelperUtil.transW(width, 40); 47 | int y2 = firstPixcel.y + HelperUtil.transW(width, 260); 48 | int x3 = firstPixcel.x + HelperUtil.transW(width, 40); 49 | int y3 = firstPixcel.y + HelperUtil.transW(width, 320); 50 | int x4 = firstPixcel.x + HelperUtil.transW(width, -40); 51 | int y4 = firstPixcel.y + HelperUtil.transW(width, 320); 52 | 53 | if (!Color.compareColor(pixels[y1][x1].color, 0x6b9cf8)) { 54 | return false; 55 | } 56 | if (!Color.compareColor(pixels[y2][x2].color, 0xf3695d)) { 57 | return false; 58 | } 59 | if (!Color.compareColor(pixels[y3][x3].color, 0xf3b409)) { 60 | return false; 61 | } 62 | if (!Color.compareColor(pixels[y4][x4].color, 0xca6e07)) { 63 | return false; 64 | } 65 | return true; 66 | } 67 | 68 | /** 69 | * 检查是否是井盖 70 | */ 71 | public static final boolean checkCover(Pixel[][] pixels, Pixel firstPixcel) { 72 | int width = pixels[0].length; 73 | int height = pixels.length; 74 | 75 | int x1 = firstPixcel.x; 76 | int y1 = firstPixcel.y + HelperUtil.transW(width, 30); 77 | int x2 = firstPixcel.x + HelperUtil.transW(width, 25); 78 | int y2 = firstPixcel.y + HelperUtil.transW(width, 280); 79 | int x3 = firstPixcel.x + HelperUtil.transW(width, -25); 80 | int y3 = firstPixcel.y + HelperUtil.transW(width, 280); 81 | int x4 = firstPixcel.x + HelperUtil.transW(width, 5); 82 | int y4 = firstPixcel.y + HelperUtil.transW(width, 320); 83 | 84 | if (!Color.compareColor(pixels[y1][x1].color, 0x757575)) { 85 | return false; 86 | } 87 | if (!Color.compareColor(pixels[y2][x2].color, 0x6c6c6c)) { 88 | return false; 89 | } 90 | if (!Color.compareColor(pixels[y3][x3].color, 0x5a5a5a)) { 91 | return false; 92 | } 93 | if (!Color.compareColor(pixels[y4][x4].color, 0xf3c30a)) { 94 | return false; 95 | } 96 | return true; 97 | } 98 | 99 | /** 100 | * 检查是否是商店 101 | */ 102 | public static final boolean checkShop(Pixel[][] pixels, Pixel firstPixcel) { 103 | int width = pixels[0].length; 104 | int height = pixels.length; 105 | 106 | int x1 = firstPixcel.x; 107 | int y1 = firstPixcel.y + HelperUtil.transW(width, 20); 108 | int x2 = firstPixcel.x + HelperUtil.transW(width, 10); 109 | int y2 = firstPixcel.y + HelperUtil.transW(width, 315); 110 | int x3 = firstPixcel.x + HelperUtil.transW(width, -10); 111 | int y3 = firstPixcel.y + HelperUtil.transW(width, 320); 112 | int x4 = firstPixcel.x + HelperUtil.transW(width, 60); 113 | int y4 = firstPixcel.y + HelperUtil.transW(width, 360); 114 | 115 | if (!Color.compareColor(pixels[y1][x1].color, 0xe6e6e6)) { 116 | return false; 117 | } 118 | if (!Color.compareColor(pixels[y2][x2].color, 0xf4f4f4)) { 119 | return false; 120 | } 121 | if (!Color.compareColor(pixels[y3][x3].color, 0xcccccc)) { 122 | return false; 123 | } 124 | if (!Color.compareColor(pixels[y4][x4].color, 0xe1ddd3)) { 125 | return false; 126 | } 127 | return true; 128 | } 129 | 130 | /** 131 | * 检查是否是唱片 132 | */ 133 | public static final boolean checkDisc(Pixel[][] pixels, Pixel firstPixcel) { 134 | int width = pixels[0].length; 135 | int height = pixels.length; 136 | 137 | int x1 = firstPixcel.x; 138 | int y1 = firstPixcel.y + HelperUtil.transW(width, 95); 139 | int x2 = firstPixcel.x ; 140 | int y2 = firstPixcel.y + HelperUtil.transW(width, 277); 141 | int x3 = firstPixcel.x ; 142 | int y3 = firstPixcel.y + HelperUtil.transW(width, 225); 143 | 144 | if (!Color.compareColor(pixels[y1][x1].color, 0x767676)) { 145 | return false; 146 | } 147 | if (!Color.compareColor(pixels[y2][x2].color, 0x6b6b6b)) { 148 | return false; 149 | } 150 | if (!Color.compareColor(pixels[y3][x3].color, 0x767676)) { 151 | return false; 152 | } 153 | return true; 154 | } 155 | 156 | /** 157 | * 检查是否是卫生纸 158 | */ 159 | public static final boolean checkBumf(Pixel[][] pixels, Pixel firstPixcel) { 160 | int width = pixels[0].length; 161 | int height = pixels.length; 162 | 163 | int x1 = firstPixcel.x; 164 | int y1 = firstPixcel.y + HelperUtil.transW(width, 8); 165 | int x2 = firstPixcel.x + HelperUtil.transW(width, 35); 166 | int y2 = firstPixcel.y + HelperUtil.transW(width, 184); 167 | int x3 = firstPixcel.x + HelperUtil.transW(width, -3); 168 | int y3 = firstPixcel.y + HelperUtil.transW(width, 171); 169 | 170 | if (!Color.compareColor(pixels[y1][x1].color, 0xffffff)) { 171 | return false; 172 | } 173 | if (!Color.compareColor(pixels[y2][x2].color, 0xe7e7e7)) { 174 | return false; 175 | } 176 | if (!Color.compareColor(pixels[y3][x3].color, 0xe3e3e3,5)) { 177 | return false; 178 | } 179 | return true; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Android/src/com/tencent/wechatjump/helper/util/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.tencent.wechatjump.helper.util; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | 10 | /** 11 | * Created by xushanmeng on 2018/1/1. 12 | */ 13 | 14 | public class FileUtil { 15 | 16 | public static final void deleteFile(File file) { 17 | if (file.isDirectory()) { 18 | File[] subFiles = file.listFiles(); 19 | for (File subFile : subFiles) { 20 | deleteFile(subFile); 21 | } 22 | } else { 23 | file.delete(); 24 | } 25 | } 26 | 27 | public static boolean copy(InputStream is, OutputStream os) { 28 | try { 29 | byte[] buffer = new byte[4096]; 30 | int len = 0; 31 | while ((len = is.read(buffer)) != -1) { 32 | os.write(buffer, 0, len); 33 | } 34 | os.flush(); 35 | os.close(); 36 | is.close(); 37 | return true; 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | } 41 | return false; 42 | } 43 | 44 | public static boolean copyFile(File fromFile, File toFile) { 45 | try { 46 | FileInputStream fis = new FileInputStream(fromFile); 47 | FileOutputStream fos = new FileOutputStream(toFile); 48 | return copy(fis, fos); 49 | } catch (Exception e) { 50 | e.printStackTrace(); 51 | } 52 | return false; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Android/src/com/tencent/wechatjump/helper/util/HelperUtil.java: -------------------------------------------------------------------------------- 1 | package com.tencent.wechatjump.helper.util; 2 | 3 | import com.tencent.wechatjump.helper.Constants; 4 | import com.tencent.wechatjump.helper.bean.Pixel; 5 | 6 | /** 7 | * Created by xushanmeng on 2018/1/1. 8 | */ 9 | 10 | public class HelperUtil { 11 | 12 | /** 13 | * 执行Shell命令,同步耗时操作 14 | */ 15 | public static final boolean execute(String... cmd) { 16 | try { 17 | Process process = Runtime.getRuntime().exec(cmd); 18 | int result = process.waitFor(); 19 | if (result != 0) { 20 | System.out.println("Failed to execute \"" + cmd + "\", result code is " + result); 21 | } else { 22 | return true; 23 | } 24 | } catch (Exception e) { 25 | e.printStackTrace(); 26 | } 27 | return false; 28 | } 29 | 30 | /** 31 | * 计算像素点的距离 32 | */ 33 | public static final double calculateDistance(Pixel pixel1, Pixel pixel2) { 34 | return Math.sqrt(Math.pow((pixel1.y - pixel2.y), 2) + Math.pow((pixel1.x - pixel2.x), 2)); 35 | } 36 | 37 | /** 38 | * 寻找色块顶点像素 39 | */ 40 | public static final Pixel[] findVertexs(Pixel[][] pixels, Pixel firstPixcel) { 41 | Pixel[] vertexs = new Pixel[4]; 42 | Pixel topPixel = firstPixcel; 43 | Pixel leftPixel = firstPixcel; 44 | Pixel rightPixel = firstPixcel; 45 | Pixel bottomPixel = firstPixcel; 46 | Pixel currentPixcel = firstPixcel; 47 | while (checkBorder(pixels, currentPixcel) 48 | && Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) { 49 | currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x]; 50 | } 51 | while (checkBorder(pixels, currentPixcel) 52 | && Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) { 53 | currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1]; 54 | } 55 | //寻找上顶点像素 56 | while (checkBorder(pixels, currentPixcel)) { 57 | if (Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) { 58 | currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x]; 59 | } else if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x + 1], firstPixcel)) { 60 | currentPixcel = pixels[currentPixcel.y][currentPixcel.x + 1]; 61 | } else { 62 | topPixel = findCenterPixcelHorizontal(pixels, currentPixcel); 63 | break; 64 | } 65 | } 66 | //寻找右顶点像素 67 | while (checkBorder(pixels, currentPixcel)) { 68 | if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x + 1], firstPixcel)) { 69 | currentPixcel = pixels[currentPixcel.y][currentPixcel.x + 1]; 70 | } else if (Color.compareColor(pixels[currentPixcel.y + 1][currentPixcel.x], firstPixcel)) { 71 | currentPixcel = pixels[currentPixcel.y + 1][currentPixcel.x]; 72 | } else { 73 | rightPixel = findCenterPixcelVertial(pixels, currentPixcel); 74 | break; 75 | } 76 | } 77 | //寻找下顶点像素 78 | while (checkBorder(pixels, currentPixcel)) { 79 | if (Color.compareColor(pixels[currentPixcel.y + 1][currentPixcel.x], firstPixcel)) { 80 | currentPixcel = pixels[currentPixcel.y + 1][currentPixcel.x]; 81 | } else if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) { 82 | currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1]; 83 | } else { 84 | bottomPixel = findCenterPixcelHorizontal(pixels, currentPixcel); 85 | break; 86 | } 87 | } 88 | //寻找左顶点像素 89 | while (checkBorder(pixels, currentPixcel)) { 90 | if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) { 91 | currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1]; 92 | } else if (Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) { 93 | currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x]; 94 | } else { 95 | leftPixel = findCenterPixcelVertial(pixels, currentPixcel); 96 | break; 97 | } 98 | } 99 | vertexs[0] = leftPixel; 100 | vertexs[1] = topPixel; 101 | vertexs[2] = rightPixel; 102 | vertexs[3] = bottomPixel; 103 | return vertexs; 104 | } 105 | 106 | /** 107 | * 获取水平同色像素中心点 108 | */ 109 | public static final Pixel findCenterPixcelHorizontal(Pixel[][] pixels, Pixel base) { 110 | Pixel first = base; 111 | Pixel last = base; 112 | while (checkBorder(pixels, first)) { 113 | if (Color.compareColor(pixels[first.y][first.x - 1], base)) { 114 | first = pixels[first.y][first.x - 1]; 115 | } else { 116 | break; 117 | } 118 | } 119 | while (checkBorder(pixels, last)) { 120 | if (Color.compareColor(pixels[last.y][last.x + 1], base)) { 121 | last = pixels[last.y][last.x + 1]; 122 | } else { 123 | break; 124 | } 125 | } 126 | return pixels[base.y][(first.x + last.x) / 2]; 127 | } 128 | 129 | /** 130 | * 获取垂直同色像素中心点 131 | */ 132 | public static final Pixel findCenterPixcelVertial(Pixel[][] pixels, Pixel base) { 133 | Pixel first = base; 134 | Pixel last = base; 135 | while (checkBorder(pixels, first)) { 136 | if (Color.compareColor(pixels[first.y - 1][first.x], base)) { 137 | first = pixels[first.y - 1][first.x]; 138 | } else { 139 | break; 140 | } 141 | } 142 | while (checkBorder(pixels, last)) { 143 | if (Color.compareColor(pixels[last.y + 1][last.x], base)) { 144 | last = pixels[last.y + 1][last.x]; 145 | } else { 146 | break; 147 | } 148 | } 149 | return pixels[(first.y + last.y) / 2][base.x]; 150 | } 151 | 152 | /** 153 | * 检测是否超出可处理图像的边缘 154 | */ 155 | public static final boolean checkBorder(Pixel[][] pixels, Pixel pixel) { 156 | int width = pixels[0].length; 157 | int height = pixels.length; 158 | if (pixel.x < transW(width, Constants.LEFT_BORDER) 159 | || pixel.x > width - transW(width, Constants.RIGHT_BORDER) 160 | || pixel.y < transH(height, adjustTopBorder(width,height)) 161 | || pixel.y > height - transH(height, adjustBottomBorder(width,height))) { 162 | return false; 163 | } 164 | return true; 165 | } 166 | 167 | /** 168 | * 转换水平数值 169 | */ 170 | public static final int transW(int screenWidth, int sourceValue) { 171 | return sourceValue * screenWidth / Constants.BASE_SCREEN_WIDTH; 172 | } 173 | 174 | /** 175 | * 转换垂直数值 176 | */ 177 | public static final int transH(int screenHeight, int sourceValue) { 178 | return sourceValue * screenHeight / Constants.BASE_SCREEN_HEIGHT; 179 | } 180 | 181 | public static final int adjustTopBorder(int screenWidth, int screenHeight) { 182 | return Constants.TOP_BORDER + (screenHeight * Constants.BASE_SCREEN_WIDTH / screenWidth - Constants.BASE_SCREEN_HEIGHT) / 2; 183 | } 184 | 185 | public static final int adjustBottomBorder(int screenWidth, int screenHeight) { 186 | return Constants.BOTTOM_BORDER + (screenHeight * Constants.BASE_SCREEN_WIDTH / screenWidth - Constants.BASE_SCREEN_HEIGHT) / 2; 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /Android/src/resource/adb_mac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Android/src/resource/adb_mac -------------------------------------------------------------------------------- /Android/src/resource/adb_windows: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Android/src/resource/adb_windows -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | >这里将对一些用户常见问题作出解答 3 | 4 | #### 1. 为什么电脑端正常执行,但是手机却不跳? 5 | 6 | 【答】有的手机会有USB调试安全选项,在未打开的情况下,禁止模拟点击,把这个打开就好了。 7 | 8 | ![](Samples/usb_debug.jpg) 9 | 10 | #### 2. 为什么棋子落点总是计算错误? 11 | 12 | 【答】三星曲面屏手机请关闭曲面侧屏,其他手机如果有类似悬浮球的控件,也请关闭,否则会影响图像识别。 13 | 14 | #### 3. 使用系统推荐跳跃系数跳不准怎么办? 15 | 16 | 【答】推荐跳跃系数没有适配所有机型,如果跳跃系数不准,请自己慢慢调整适合自己手机的跳跃系数。 17 | 18 | #### 4. 为什么跳完之后分数上传失败? 19 | 20 | 【答】微信跳一跳现在的外挂检测很严格,最后几步最好自己手动跳几下,还有分数不要跳太高,要不很容易被判定为使用了外挂。 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信跳一跳游戏助手 2 | 3 | [GitHub 项目地址](https://github.com/xushanmeng/WechatJumpHelper) 4 | 5 | 微信跳一跳技术讨论QQ群 6 | * 148698890 7 | 8 | [更新日志](changelog.md) 9 | 10 | Windows一键开始请移步[release](release) 11 | 12 | 用户常见问题请先查询[FAQ](FAQ.md) 13 | 14 | ## 功能简介 15 | 16 | >用JAVA自动控制手机玩跳一跳 17 | 18 | * 自动识别图像计算距离 19 | * 自动帮你点击屏幕 20 | * 自动缓存图片,并在图片上标记一些识别结果,如下图![示例图片](Samples/172_mark.png) 21 | 22 | 23 | ## 运行环境 24 | 1. JAVA,最低版本为7.0,[官网下载](http://www.oracle.com/technetwork/java/javase/downloads/index.html) 25 | 2. 安卓手机,目前已适配分辨率 26 | * 1600x2560 27 | * 1440x2560 28 | * 1080x1920 29 | * 720x1280 30 | 31 | ## 使用方法 32 | 33 | >有JAVA开发工具的同学可以直接运行java代码,便于代码调试,下面主要介绍运行已经打包好的jar包的方法 34 | 35 | 1. 手机打开USB调试,并连接电脑 36 | * 打开USB调试方法,进入`设置`,找到`开发者选项`,打开并勾选`USB调试`; 37 | * 如果没有`开发者选项`,进入`关于手机`,连续点击`版本号`7次,即可开启`开发者选项`。 38 | 2. 通过下面的命令,运行[Android.jar](Android/build/libs/Android.jar) 39 | ``` 40 | java -jar Android.jar 41 | ``` 42 | 3. 根据手机分辨率选择跳跃系数,目前已适配机型: 43 | 44 | * 1600x2560机型推荐0.92 45 | * 1440x2560机型推荐1.039 46 | * 1080x1920机型推荐1.392 47 | * 720x1280机型推荐2.078 48 | 49 | 其他分辨率请自己微调。 50 | 51 | ## 原理说明 52 | 1. 通过adb命令控制手机截图,并取回到本地 53 | 54 | ``` 55 | adb shell screencap -p /sdcard/screen.png 56 | adb pull /sdcard/screen.png . 57 | ``` 58 | 59 | 2. 图片分析 60 | * 根据棋子的颜色,取顶部和底部的特征像素点,在截图中进行匹配,找到棋子坐标 61 | * 由于目标物体不是在左上就是在右上,可以从上往下扫描,根据色差判断目标物体位置,其中又分为以下几种类型 62 | * 有靶点,即目标物体中心的白色圆点,则靶点中心为目标落点 63 | 64 | ![](Samples/412_mark.png) 65 | * 无靶点,但是纯色平面,或者规则平面,则平面中心为目标落点 66 | 67 | ![](Samples/444_mark.png) 68 | * 无靶点,又无纯色规则平面,但是左上和右上位置的斜率是固定的,可根据固定斜率的斜线和目标物体中心线的焦点计算落点 69 | 70 | ![](Samples/544_mark.png) 71 | * 计算棋子坐标和目标落点的距离 72 | * 距离×跳跃系数=按压屏幕的时间,不同分辨率的手机,跳跃系数也有所不同 73 | 74 | 3. 通过adb命令,给手机模拟按压事件 75 | ``` 76 | adb shell input swipe x y x y time 77 | ``` 78 | 其中`x`和`y`是屏幕坐标,`time`是触摸时间,单位ms。 79 | 80 | ## 工程结构 81 | ```目录结构 82 | . 83 | ├── Samples # 一些示例图片 84 | ├── Android # 使用Android手机的相关代码和jar包 85 | │ ├── src # JAVA源代码目录 86 | │ └── build # Android.jar包所在目录 87 | └── README.md 88 | ``` 89 | ## 代码详解 90 | 91 | >这里将针对一些关键算法的代码进行解释 92 | 93 | #### 1. 寻找棋子位置 94 | 95 | 把截图放大,可以看到棋子顶部像素连成一条横线,那么我们通过颜色匹配,找到这一条线的始末位置,取中间位置,就得到了棋子的x坐标。 96 | 97 | ![](Samples/piece_top.png) 98 | 99 | 棋子的底部也是一条横线,用颜色匹配,我们检测到相似颜色的最大y坐标,就是棋子底部了,不过考虑到棋子底部是个圆盘,我们把棋子的y坐标再往上提一些。 100 | 101 | ![](Samples/piece_bottom.png) 102 | 103 | 这样我们就得到了棋子的xy坐标,下面是相关代码: 104 | 105 | ```java 106 | /* 计算棋子位置 */ 107 | Pixel piece = new Pixel(); 108 | for (int i = TOP_BORDER; i < screenHeight - BOTTOM_BORDER; i++) { 109 | int startX = 0; 110 | int endX = 0; 111 | for (int j = LEFT_BORDER; j < screenWidth - RIGHT_BORDER; j++) { 112 | int red = Color.red(pixels[i][j].color); 113 | int green = Color.green(pixels[i][j].color); 114 | int blue = Color.blue(pixels[i][j].color); 115 | if (50 < red && red < 55 116 | && 50 < green && green < 55 117 | && 55 < blue && blue < 65) {//棋子顶部颜色 118 | //如果侦测到棋子相似颜色,记录下开始点 119 | if (startX == 0) { 120 | startX = j; 121 | endX = 0; 122 | } 123 | } else if (endX == 0) { 124 | //记录下结束点 125 | endX = j; 126 | 127 | if (endX - startX < PIECE_TOP_PIXELS) { 128 | //规避井盖的BUG,像素点不够长,则重新计算 129 | startX = 0; 130 | endX = 0; 131 | } 132 | } 133 | if (50 < red && red < 60 134 | && 55 < green && green < 65 135 | && 95 < blue && blue < 105) {//棋子底部的颜色 136 | //最后探测到的颜色就是棋子的底部像素 137 | piece.y = i; 138 | } 139 | } 140 | if (startX != 0 && piece.x == 0) { 141 | piece.x = (startX + endX) / 2; 142 | } 143 | } 144 | //棋子纵坐标从底部边缘调整到底部中心 145 | piece.y -= PIECE_BOTTOM_CENTER_SHIFT; 146 | ``` 147 | 148 | #### 2. 寻找靶点 149 | 150 | 所谓靶点,就是目标物体中心的那个小圆点,颜色值为`0xf5f5f5`。 151 | 152 | ![](Samples/target.png) 153 | 154 | 那么我们只需要寻找颜色值为0xf5f5f5的色块就可以了,为了规避其他物体相近颜色干扰,我们可以限制色块的大小,正确大小的色块才是靶点。 155 | 156 | 但是如何计算色块的大小呢,色块最顶端到最底端y坐标的差值我们作为色块的高度,同理,最左侧到最右侧x坐标的差值作为宽度,我们只需要查找这四个顶点的坐标就可以了。 157 | 158 | 本来打算用凸包的Graham扫描算法,后来发现色块已经是凸包了,且边缘像素是连续的,那么我们按照一定顺序,遍历边缘像素,就可以在O(n^-2)的时间复杂度里,得到色块的顶点坐标了。 159 | 160 | 我们从第一个像素点开始,寻找的顺序如图所示: 161 | 162 | ![](Samples/find_vertex.png) 163 | 164 | ```java 165 | /** 166 | * 寻找色块顶点像素 167 | */ 168 | public static final Pixel[] findVertexs(Pixel[][] pixels, Pixel firstPixcel) { 169 | Pixel[] vertexs = new Pixel[4]; 170 | Pixel topPixel = firstPixcel; 171 | Pixel leftPixel = firstPixcel; 172 | Pixel rightPixel = firstPixcel; 173 | Pixel bottomPixel = firstPixcel; 174 | Pixel currentPixcel = firstPixcel; 175 | //先把坐标置于左上角 176 | while (checkBorder(pixels, currentPixcel)//判断是否超出图像边缘 177 | && Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) {//判断是否是相同颜色 178 | currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x]; 179 | } 180 | while (checkBorder(pixels, currentPixcel) 181 | && Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) { 182 | currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1]; 183 | } 184 | //寻找上顶点像素 185 | while (checkBorder(pixels, currentPixcel)) { 186 | if (Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) { 187 | currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x]; 188 | } else if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x + 1], firstPixcel)) { 189 | currentPixcel = pixels[currentPixcel.y][currentPixcel.x + 1]; 190 | } else { 191 | topPixel = findCenterPixcelHorizontal(pixels, currentPixcel); 192 | break; 193 | } 194 | } 195 | //寻找右顶点像素 196 | while (checkBorder(pixels, currentPixcel)) { 197 | if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x + 1], firstPixcel)) { 198 | currentPixcel = pixels[currentPixcel.y][currentPixcel.x + 1]; 199 | } else if (Color.compareColor(pixels[currentPixcel.y + 1][currentPixcel.x], firstPixcel)) { 200 | currentPixcel = pixels[currentPixcel.y + 1][currentPixcel.x]; 201 | } else { 202 | rightPixel = findCenterPixcelVertial(pixels, currentPixcel); 203 | break; 204 | } 205 | } 206 | //寻找下顶点像素 207 | while (checkBorder(pixels, currentPixcel)) { 208 | if (Color.compareColor(pixels[currentPixcel.y + 1][currentPixcel.x], firstPixcel)) { 209 | currentPixcel = pixels[currentPixcel.y + 1][currentPixcel.x]; 210 | } else if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) { 211 | currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1]; 212 | } else { 213 | bottomPixel = findCenterPixcelHorizontal(pixels, currentPixcel); 214 | break; 215 | } 216 | } 217 | //寻找左顶点像素 218 | while (checkBorder(pixels, currentPixcel)) { 219 | if (Color.compareColor(pixels[currentPixcel.y][currentPixcel.x - 1], firstPixcel)) { 220 | currentPixcel = pixels[currentPixcel.y][currentPixcel.x - 1]; 221 | } else if (Color.compareColor(pixels[currentPixcel.y - 1][currentPixcel.x], firstPixcel)) { 222 | currentPixcel = pixels[currentPixcel.y - 1][currentPixcel.x]; 223 | } else { 224 | leftPixel = findCenterPixcelVertial(pixels, currentPixcel); 225 | break; 226 | } 227 | } 228 | vertexs[0] = leftPixel; 229 | vertexs[1] = topPixel; 230 | vertexs[2] = rightPixel; 231 | vertexs[3] = bottomPixel; 232 | return vertexs; 233 | } 234 | ``` 235 | 得到了四个坐标点,我们就可以计算色块的中点了,也就是目标落点。 236 | 237 | 对于没有靶点,但是落点是规则平面的,也可以用类似算法。 238 | 239 | #### 3. 斜率计算 240 | 241 | 对于没有靶点,又不是规则平面的,我们怎么计算落点呢,这时候就要用到斜率了。 242 | 243 | 可以看得出来,每次左上角或右上角出现的物体,针对当前物体的方向都是一样的,也就是两个物体中心的连线,斜率是固定的。 244 | 245 | 基本所有的目标物体,最顶点像素中点的x坐标,都是在物体中间,我们至少先得到了目标物体x坐标了,记为des.x ,接下来要求des.y 。 246 | 247 | ![](Samples/k1.png) 248 | 249 | 如上图所示,计算过程如下: 250 | ``` 251 | 斜线的公式为 y=kx+b 252 | 那么,在棋子坐标上有 piece.y=k*piece.x+b 253 | 在目标落点坐标上有 des.y=k*des.x+b 254 | 代入得到 des.y=k*(des.x-piece.x)+piece.y 255 | ``` 256 | 然而这种算法还是有偏差的。 257 | 258 | ![](Samples/k2.png) 259 | 260 | 可以看到,同样的斜率,如果棋子的位置有偏差,计算出来最终落点还是会有偏差的。 261 | 262 | 代码解析就先讲这么多,希望有大神可以提出更好的解决方案。 263 | 264 | ## 玩游戏小窍门 265 | 1. 连续的落到物体中心位置,是有分数加成的,最多跳一次可以得几十分 266 | 2. 井盖、商店、唱片、魔方,多停留一会,有音乐响起后也是有分数加成的 267 | >那么看一下程序员的朋友圈有多残酷吧 268 | 269 | ![](Samples/rank.jpeg) 270 | -------------------------------------------------------------------------------- /Samples/172_mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/172_mark.png -------------------------------------------------------------------------------- /Samples/412_mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/412_mark.png -------------------------------------------------------------------------------- /Samples/444_mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/444_mark.png -------------------------------------------------------------------------------- /Samples/544_mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/544_mark.png -------------------------------------------------------------------------------- /Samples/find_vertex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/find_vertex.png -------------------------------------------------------------------------------- /Samples/k1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/k1.png -------------------------------------------------------------------------------- /Samples/k2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/k2.png -------------------------------------------------------------------------------- /Samples/piece_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/piece_bottom.png -------------------------------------------------------------------------------- /Samples/piece_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/piece_top.png -------------------------------------------------------------------------------- /Samples/rank.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/rank.jpeg -------------------------------------------------------------------------------- /Samples/target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/target.png -------------------------------------------------------------------------------- /Samples/usb_debug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/Samples/usb_debug.jpg -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## 更新日志 2 | 3 | * 2018-01-05: 4 | * 集成了Windows和Mac的adb,可以不用另装adb运行了 5 | * 添加了手机连接的检测 6 | * 修复了全面屏手机计算失败的BUG 7 | 8 | * 2018-01-04: 9 | * 调整了分辨率检测策略,用户可以自定义跳跃系数 10 | * 模拟点击位置改为随机位置,修复了因为微信外挂检测而无法上传积分的问题 11 | 12 | * 2018-01-03: 13 | * 添加了手机分辨率的自动检测 14 | * 修正了720x1280分辨率的问题 15 | 16 | * 2018-01-02: 17 | * 初始提交 -------------------------------------------------------------------------------- /release/1.0.0/WechatJumpHelper-1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xushanmeng/WechatJumpHelper/c229d260c3ebc274a9fb36d4648efa354f34fa40/release/1.0.0/WechatJumpHelper-1.0.0.zip -------------------------------------------------------------------------------- /release/release.md: -------------------------------------------------------------------------------- 1 | ## Release日志 2 | 3 | * v1.0.0: 4 | * 发布日期2018-01-05 5 | * 内置了jre和adb 6 | * 仅限windows,解压后,双击打开WechatJumpHelper.bat即可运行 7 | --------------------------------------------------------------------------------