ksjsb/tools/phone_solve_v4.py
2026-07-30 20:25:56 +08:00

158 lines
5.3 KiB
Python

"""v4: 干净标定 + 精确求解。
handle = 左窗 [80,340] 内最密紧簇(避免右侧文字/按钮)。
标定: 短扫 hh->hh+250 (handle 始终 <340, 不碰文字带), 同帧测 (handle_x,piece_x)
拟合 piece=a+b*handle => R=b, home=a+b*hh。延迟抵消(同帧)。
"""
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, lo=80, hi=340):
band = img[932:968, lo:hi]
col = (band < 200).sum(axis=0).astype(float)
hot = np.where(col > 12)[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 10 <= len(s) <= 80]
if not cands:
return None
seg = max(cands, key=lambda s: col[s].max()) # 峰值密度最高=实心按钮
w = col[seg[0]:seg[-1] + 1]
return lo + int(round(np.average(seg, weights=w)))
def piece_x(home_img, cur_img):
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 top_activity():
out = adb_text("shell", "dumpsys", "activity", "activities")
return next((l.strip() for l in out.splitlines() if "topResumedActivity" in l), "(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
# 短扫: handle 始终 < 340
SWEEP_END = min(hh + 250, 330)
DUR = 3500
p = subprocess.Popen(["adb", "shell", "input", "swipe",
str(hh), "950", str(SWEEP_END), "950", str(DUR)])
caps = []
last = 0.0
for t in (0.8, 1.6, 2.4, 3.1):
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, lo=max(60, hh - 30), hi=345)
px = piece_x(home, c)
if hx and px:
pts.append((hx, px))
print(f"[cal] (handle,piece) 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)
home_center = a + b * hh
print(f"[cal] FIT piece={a:.1f}+{b:.3f}*handle R={b:.3f} home_center={home_center:.1f}",
flush=True)
fresh = cap()
notchB = detect_notch(fresh)
hh2 = detect_handle(fresh)
print(f"[fresh] notchB={notchB} handle={hh2}", flush=True)
if notchB is None:
print("[!] 刷新后无缺口"); return 2
hh2 = hh2 or hh
target = (notchB - a) / b
print(f"[plan] notch={notchB} => target_handle={target:.0f} (swipe {hh2}->{target:.0f})",
flush=True)
adb_text("logcat", "-c")
subprocess.run(["adb", "shell", "input", "swipe", str(hh2), "950",
str(int(round(target))), "950", "650"], timeout=30)
time.sleep(4.0)
aft = cap()
after = top_activity()
closed = "KwaiWebViewActivity" not in after
changed = not np.array_equal(photo(fresh), photo(aft))
print(f"[post] top={after[:70]} closed={closed} photo_changed={changed}", flush=True)
if closed:
print(">>> VERDICT: 活动关闭 => result=1! 真机 WebView 指纹过 350014。路径=真机/模拟器。", flush=True)
elif changed:
print(f">>> VERDICT: 命中刷新(已按 R={b:.3f} 居中) => 350014(号码被永久标记, 真机也过不去)。", flush=True)
else:
print(">>> VERDICT: 未刷新。", flush=True)
print("\n===== LOGCAT =====", flush=True)
for ln in adb_text("logcat", "-d").splitlines():
low = ln.lower()
if any(k in low for k in ("ksecretapi", "captcha", "3500", "\"result\"", "verify")):
print(ln[:180], flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())