159 lines
5.6 KiB
Python
159 lines
5.6 KiB
Python
"""无延迟标定: 同一帧同时检测 handle_x 和 piece_x => 延迟抵消。
|
|
慢扫 sweep, 多帧 (handle_x, piece_x) 拟合 piece=a+b*handle => R=b, home=a+b*hh。
|
|
notch 在 diff(home,frame) 里自动抵消(静态), 只剩移动的拼图块 => 干净。
|
|
"""
|
|
from __future__ import annotations
|
|
import subprocess
|
|
import time
|
|
from io import BytesIO
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
PHOTO_LEFT, PHOTO_RIGHT = 66, 1013
|
|
PHOTO_TOP, PHOTO_BOT = 267, 819
|
|
|
|
|
|
def adb_text(*a):
|
|
r = subprocess.run(["adb", *a], capture_output=True, text=True,
|
|
encoding="utf-8", errors="replace", timeout=60)
|
|
return r.stdout
|
|
|
|
|
|
def cap():
|
|
d = subprocess.run(["adb", "exec-out", "screencap", "-p"],
|
|
capture_output=True, timeout=30).stdout
|
|
return np.asarray(Image.open(BytesIO(d)).convert("L"))
|
|
|
|
|
|
def photo(img):
|
|
return img[PHOTO_TOP:PHOTO_BOT, PHOTO_LEFT:PHOTO_RIGHT]
|
|
|
|
|
|
def detect_notch(img, x_min_local=230, col_thr=25):
|
|
reg = photo(img)
|
|
col = (reg < 90).sum(axis=0)
|
|
col_r = col[x_min_local:]
|
|
hot = np.where(col_r > col_thr)[0]
|
|
if len(hot) == 0:
|
|
return None
|
|
br = np.where(np.diff(hot) > 12)[0]
|
|
segs = [s for s in np.split(hot, br + 1) if len(s) > 10]
|
|
seg = max(segs, key=lambda s: col_r[s].sum())
|
|
segL = x_min_local + int(seg[0]); segR = x_min_local + int(seg[-1])
|
|
ys, xs = np.where((reg[:, segL:segR + 1] < 95))
|
|
if len(xs) == 0:
|
|
return None
|
|
return PHOTO_LEFT + int(round(np.average(xs + segL)))
|
|
|
|
|
|
def detect_handle(img):
|
|
"""滑块按钮 = 紧凑高密度亮簇(每列非白计数最高)。找峰值列再扩簇。"""
|
|
band = img[932:968, :]
|
|
col = (band < 200).sum(axis=0).astype(float)
|
|
# 候选簇: col>15 的连续段, 宽度 12..70(排除细槽/宽文字带)
|
|
hot = np.where(col > 15)[0]
|
|
if len(hot) == 0:
|
|
return None
|
|
br = np.where(np.diff(hot) > 6)[0]
|
|
cands = [s for s in np.split(hot, br + 1) if 12 <= len(s) <= 70]
|
|
if not cands:
|
|
return None
|
|
# 取峰值密度(每列最大计数)最高的簇 = 实心按钮
|
|
seg = max(cands, key=lambda s: col[s].max())
|
|
w = col[seg[0]:seg[-1] + 1]
|
|
return int(round(np.average(seg, weights=w)))
|
|
|
|
|
|
def piece_x(home_img, cur_img):
|
|
"""diff(home,cur) 右簇(拼图块当前位置) 局部 cx -> screen x。"""
|
|
d = np.abs(photo(cur_img).astype(np.int16) - photo(home_img).astype(np.int16))
|
|
mask = d > 45
|
|
col = mask.sum(axis=0)
|
|
hot = np.where(col > 6)[0]
|
|
if len(hot) == 0:
|
|
return None
|
|
w = np.where(col > 6, col, 0)
|
|
br = np.where(np.diff(hot) > 12)[0]
|
|
cands = [int(round(np.average(s, weights=w[s])))
|
|
for s in np.split(hot, br + 1) if len(s) > 12]
|
|
return (PHOTO_LEFT + max(cands)) if cands else None
|
|
|
|
|
|
def main():
|
|
home = cap()
|
|
notchA = detect_notch(home)
|
|
hh = detect_handle(home)
|
|
print(f"[init] notchA={notchA} handle_home={hh}", flush=True)
|
|
if hh is None:
|
|
print("[!] 无 handle, 退出"); return 2
|
|
|
|
return _sweep(home, hh, 1000, notchA)
|
|
|
|
|
|
def _sweep(home, hh, END, notchA):
|
|
p = subprocess.Popen(["adb", "shell", "input", "swipe",
|
|
str(hh), "950", str(END), "950", "5500"])
|
|
times = [1.0, 2.0, 3.0, 4.0, 4.8]
|
|
last = 0.0
|
|
caps = []
|
|
for t in times:
|
|
time.sleep(t - last); last = t
|
|
caps.append(cap())
|
|
p.wait()
|
|
time.sleep(2.2) # 释放 => refresh
|
|
|
|
pts = []
|
|
for c in caps:
|
|
hx = detect_handle(c)
|
|
px = piece_x(home, c)
|
|
if hx and px:
|
|
pts.append((hx, px))
|
|
print(f"[cal] (handle_x, piece_x) pairs = {pts}", flush=True)
|
|
if len(pts) < 2:
|
|
print("[!] 标定点不足"); return 2
|
|
hs = np.array([p[0] for p in pts]); ps = np.array([p[1] for p in pts])
|
|
b, a = np.polyfit(hs, ps, 1) # piece = a + b*handle
|
|
R = b
|
|
home_center = a + b * hh
|
|
print(f"[cal] FIT piece = {a:.1f} + {b:.3f}*handle => R={R:.3f} home_center={home_center:.1f}",
|
|
flush=True)
|
|
|
|
fresh = cap()
|
|
notchB = detect_notch(fresh)
|
|
print(f"[fresh] notchB={notchB}", flush=True)
|
|
if notchB is None:
|
|
print("[!] 刷新后无缺口"); return 2
|
|
|
|
target_handle = (notchB - a) / b
|
|
print(f"[plan] notch={notchB} => target_handle={target_handle:.0f} "
|
|
f"(swipe {hh}->{target_handle:.0f})", flush=True)
|
|
|
|
adb_text("logcat", "-c")
|
|
subprocess.run(["adb", "shell", "input", "swipe", str(hh), "950",
|
|
str(int(round(target_handle))), "950", "600"], timeout=30)
|
|
time.sleep(4.0)
|
|
aft = cap()
|
|
top = adb_text("shell", "dumpsys", "activity", "activities")
|
|
after_top = next((l.strip() for l in top.splitlines() if "topResumedActivity" in l), "(none)")
|
|
closed = "KwaiWebViewActivity" not in after_top
|
|
changed = not np.array_equal(photo(fresh), photo(aft))
|
|
print(f"[post] top={after_top[:72]} closed={closed} photo_changed={changed}", flush=True)
|
|
if closed:
|
|
print(">>> VERDICT: 活动关闭 => result=1! 真机 WebView 指纹过 350014。路径可行(真机/模拟器)。", flush=True)
|
|
elif changed:
|
|
print(f">>> VERDICT: 命中刷新(Δpiece={notchB-home_center:.0f} 已按 R={R:.3f} 换算居中) "
|
|
f"=> 350014(号码被永久标记, 真机也过不去)。", flush=True)
|
|
else:
|
|
print(">>> VERDICT: 未刷新。", flush=True)
|
|
|
|
print("\n===== LOGCAT(captcha/result) =====", flush=True)
|
|
for ln in adb_text("logcat", "-d").splitlines():
|
|
low = ln.lower()
|
|
if any(k in low for k in ("ksecretapi", "captcha", "3500", "verify", "\"result\"")):
|
|
print(ln[:190], flush=True)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|