From f1e7bb8ac67e7c7924b39a172db61296ab0d166e Mon Sep 17 00:00:00 2001 From: ImJingLan Date: Wed, 13 May 2026 22:16:05 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=86=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E5=9C=A8ios18+=E6=97=A0=E6=B3=95=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E7=9A=84=E5=85=BC=E5=AE=B9=E6=80=A7=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ HNroute.txt | 1 - README.md | 18 ++++++++--- driver/connect.py | 54 ++++++++++++++++--------------- driver/location.py | 14 +++++--- init/init.py | 20 ++++++------ init/tunnel.py | 29 +++++++++++++---- main.py | 70 ++++++++++++++++++++-------------------- requirements.txt | 6 ++-- run.py | 79 +++++++++++++++++++++++----------------------- 10 files changed, 163 insertions(+), 130 deletions(-) diff --git a/.gitignore b/.gitignore index 7a605bc..5133521 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .vscode/ __pycache__/ + +.DS_Store \ No newline at end of file diff --git a/HNroute.txt b/HNroute.txt index 59d3883..e69de29 100644 --- a/HNroute.txt +++ b/HNroute.txt @@ -1 +0,0 @@ -{"lng":"120.7335575167566","lat":"30.52802386594508"},{"lng":"120.7335664998117","lat":"30.527949984828833"},{"lng":"120.73357997439435","lat":"30.527887769107984"},{"lng":"120.73361590661474","lat":"30.527825553346897"},{"lng":"120.7336653134178","lat":"30.527771114522935"},{"lng":"120.73372370327594","lat":"30.527720564158813"},{"lng":"120.73379107618919","lat":"30.527689456229226"},{"lng":"120.73387641521265","lat":"30.527662236782586"},{"lng":"120.73397073729119","lat":"30.52765057130309"},{"lng":"120.73407404242484","lat":"30.527646682809618"},{"lng":"120.73416387297584","lat":"30.527677790753035"},{"lng":"120.73428514421968","lat":"30.52773611811983"},{"lng":"120.73436150018803","lat":"30.52781388788721"},{"lng":"120.73441539851864","lat":"30.527922765455916"},{"lng":"120.73444234768392","lat":"30.52803941985705"},{"lng":"120.73445133073903","lat":"30.528156074116726"},{"lng":"120.73445582226658","lat":"30.5283038359759"},{"lng":"120.73446031379413","lat":"30.528420489914918"},{"lng":"120.73446031379413","lat":"30.52852158988096"},{"lng":"120.73446480532168","lat":"30.528618801286576"},{"lng":"120.73446480532168","lat":"30.528719901044198"},{"lng":"120.73445582226658","lat":"30.52881322380307"},{"lng":"120.73442887310128","lat":"30.528902658028688"},{"lng":"120.73439294088088","lat":"30.52898820373192"},{"lng":"120.73434353407784","lat":"30.529034864992525"},{"lng":"120.73427616116459","lat":"30.529081526230495"},{"lng":"120.73420429672379","lat":"30.52912429901208"},{"lng":"120.73414590686563","lat":"30.529143741179244"},{"lng":"120.73408302547993","lat":"30.52915929491014"},{"lng":"120.73399319492894","lat":"30.52915540647765"},{"lng":"120.73390785590549","lat":"30.52915540647765"},{"lng":"120.73383599146469","lat":"30.529135964312847"},{"lng":"120.733773110079","lat":"30.529108745275526"},{"lng":"120.73372370327594","lat":"30.529065972487018"},{"lng":"120.73365633036269","lat":"30.529003757487974"},{"lng":"120.73361590661474","lat":"30.528925988682573"},{"lng":"120.7335754828668","lat":"30.528855996703964"},{"lng":"120.7335664998117","lat":"30.528782116226836"},{"lng":"120.7335575167566","lat":"30.528719901044198"},{"lng":"120.7335575167566","lat":"30.52863435510235"},{"lng":"120.7335575167566","lat":"30.528541032169947"},{"lng":"120.7335575167566","lat":"30.528451597608097"},{"lng":"120.7335485337015","lat":"30.528350497568482"},{"lng":"120.73355302522906","lat":"30.528261062829475"},{"lng":"120.7335575167566","lat":"30.52818329342363"},{"lng":"120.73356200828415","lat":"30.528128854802127"} \ No newline at end of file diff --git a/README.md b/README.md index fae340b..de19449 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ ### 前置条件 1. 系统是 `Windows` 或 `MacOS` -2. iPhone 或 iPad 系统版本大于等于 17 -3. Windows 需要安装 iTunes -4. 已安装 `Python3` 和 `pip3` +2. iPhone 或 iPad 系统版本 >= 17(支持 iOS 17 ~ iOS 26+) +3. Windows 需要安装 iTunes(从 Microsoft Store 安装) +4. 已安装 `Python 3.10+` 和 `pip3` 5. **重要**: 只能有一台 iPhone 或 iPad 连接到电脑,否则会出问题 ### 步骤 @@ -18,7 +18,7 @@ pip3 install -r requirements.txt ``` 如果 `pip3` 无法安装,请使用 `pip` 替代 - 如果提示没有需要的版本,请尝试不适用国内源 + 如果提示没有需要的版本,请尝试不使用国内源 3. 修改配置和路线文件 (见 [这里](https://github.com/iOSRealRun/iOSRealRun-cli/blob/main/README.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95) 的 4、5、7 步) 4. 将设备连接到电脑,解锁,如果请求信任的提示框,请点击信任 5. Windows **以管理员身份** 打开终端(cmd 或 PowerShell),先进入项目目录,然后执行以下命令 @@ -29,8 +29,16 @@ ```shell sudo python3 main.py ``` - > 需要 管理员 或 root 权限是因为需要创建 tun 设备 + > 需要 管理员 或 root 权限是因为需要创建 TUN 设备 6. 按照提示操作,如果一直说没有设备连接,Windows请确保 iTunes 已安装(可能需要打开),重新运行程序,在第3步时请确保设备已连接,解锁并信任 7. 结束请务必使用 `Ctrl + C` 终止程序,否则无法恢复定位 8. 如果定位未恢复,可以重启手机解决 + +### iOS 26 适配说明 + +此版本已适配 iOS 26(及 iOS 18.2+)的连接方式变更: +- 隧道协议使用 **TCP**(iOS 18.2+ 已移除 QUIC 协议支持) +- 依赖库升级至 pymobiledevice3 >= 9.12.0 +- DVT 连接使用新的 `DvtProvider` / `LocationSimulation` 异步 API +- 完整复现原有模拟定位功能(坐标系转换、路径插值、随机扰动等) diff --git a/driver/connect.py b/driver/connect.py index b4be45d..a7f59a5 100644 --- a/driver/connect.py +++ b/driver/connect.py @@ -2,54 +2,58 @@ import multiprocessing from pymobiledevice3.lockdown import create_using_usbmux, LockdownClient - -from pymobiledevice3.cli.remote import install_driver_if_required -from pymobiledevice3.cli.remote import select_device, RemoteServiceDiscoveryService -from pymobiledevice3.cli.remote import start_tunnel -from pymobiledevice3.cli.remote import verify_tunnel_imports - +from pymobiledevice3.remote.tunnel_service import CoreDeviceTunnelProxy from pymobiledevice3.services.amfi import AmfiService - from pymobiledevice3.exceptions import NoDeviceConnectedError -def get_usbmux_lockdownclient(): +logger = logging.getLogger(__name__) + + +async def get_usbmux_lockdownclient(): while True: try: - lockdown = create_using_usbmux() + lockdown = await create_using_usbmux() except NoDeviceConnectedError: print("请连接设备后按回车...") input() else: break while True: - lockdown = create_using_usbmux() + lockdown = await create_using_usbmux() if lockdown.all_values.get("PasswordProtected"): print("请解锁设备后按回车...") input() else: break - return create_using_usbmux() + return await create_using_usbmux() + def get_version(lockdown: LockdownClient): return lockdown.all_values.get("ProductVersion") -def get_developer_mode_status(lockdown: LockdownClient): - return lockdown.developer_mode_status -def reveal_developer_mode(lockdown: LockdownClient): - AmfiService(lockdown).create_amfi_show_override_path_file() +async def get_developer_mode_status(lockdown: LockdownClient): + return await lockdown.get_developer_mode_status() + + +async def reveal_developer_mode(lockdown: LockdownClient): + await AmfiService(lockdown).reveal_developer_mode_option_in_ui() -def enable_developer_mode(lockdown: LockdownClient): - AmfiService(lockdown).enable_developer_mode() -def get_serverrsd(): - install_driver_if_required() - if not verify_tunnel_imports(): - exit(1) - return select_device(None) +async def enable_developer_mode(lockdown: LockdownClient): + await AmfiService(lockdown).enable_developer_mode() -async def tunnel(rsd: RemoteServiceDiscoveryService, queue: multiprocessing.Queue): - async with start_tunnel(rsd, None) as tunnel_result: - queue.put((tunnel_result.address, tunnel_result.port)) +async def create_tunnel_from_lockdown(queue: multiprocessing.Queue): + lockdown = await get_usbmux_lockdownclient() + proxy = await CoreDeviceTunnelProxy.create(lockdown) + logger.info("CoreDeviceTunnelProxy created, starting TCP tunnel") + async with proxy.start_tcp_tunnel() as tunnel_result: + queue.put({ + "status": "ok", + "address": tunnel_result.address, + "port": tunnel_result.port, + "protocol": "TCP", + }) + logger.info("tunnel established, address=%s port=%s", tunnel_result.address, tunnel_result.port) await tunnel_result.client.wait_closed() diff --git a/driver/location.py b/driver/location.py index 6a602fc..bd68f23 100644 --- a/driver/location.py +++ b/driver/location.py @@ -1,7 +1,11 @@ -from pymobiledevice3.cli.developer import LocationSimulation +from pymobiledevice3.services.dvt.instruments.location_simulation import LocationSimulation -def set_location(dvt, lat: float, lng: float): - LocationSimulation(dvt).set(lat, lng) -def clear_location(dvt): - LocationSimulation(dvt).clear() +async def set_location(dvt, lat: float, lng: float): + async with LocationSimulation(dvt) as loc: + await loc.set(lat, lng) + + +async def clear_location(dvt): + async with LocationSimulation(dvt) as loc: + await loc.clear() diff --git a/init/init.py b/init/init.py index de453bf..9505ba9 100644 --- a/init/init.py +++ b/init/init.py @@ -4,8 +4,8 @@ from driver import connect -def init(): - # check if root on mac or Administrator on windows + +async def init(): if sys.platform == "win32": if not ctypes.windll.shell32.IsUserAnAdmin(): print("请以管理员权限运行") @@ -18,19 +18,17 @@ def init(): print("仅支持macOS和Windows") sys.exit(1) - # get lockdown client - lockdown = connect.get_usbmux_lockdownclient() + lockdown = await connect.get_usbmux_lockdownclient() - # check version version = connect.get_version(lockdown) print(f"Your system version is {version}") - if version.split(".")[0] < "17": - print(f"仅支持17及以上版本") + major = int(version.split(".")[0]) if version else 0 + if major < 17: + print("仅支持iOS 17及以上版本") sys.exit(1) - # check developer mode status - developer_mode_status = connect.get_developer_mode_status(lockdown) + developer_mode_status = await connect.get_developer_mode_status(lockdown) if not developer_mode_status: - connect.reveal_developer_mode(lockdown) + await connect.reveal_developer_mode(lockdown) print("您未开启开发者模式,请打开设备的 设置-隐私与安全性-开发者模式 来开启,开启后需要重启并输入密码,完成后再次运行此程序") - sys.exit(1) \ No newline at end of file + sys.exit(1) diff --git a/init/tunnel.py b/init/tunnel.py index 5119d90..767ea25 100644 --- a/init/tunnel.py +++ b/init/tunnel.py @@ -1,20 +1,35 @@ import asyncio import multiprocessing +from queue import Empty from driver import connect + def tunnel_proc(queue: multiprocessing.Queue): - server_rsd = connect.get_serverrsd() - asyncio.run(connect.tunnel(server_rsd, queue)) + try: + asyncio.run(connect.create_tunnel_from_lockdown(queue)) + except Exception as exc: + queue.put({ + "status": "error", + "error": repr(exc), + }) def tunnel(): - # start the tunnel in another process queue = multiprocessing.Queue() process = multiprocessing.Process(target=tunnel_proc, args=(queue,)) process.start() - - # get the address and port of the tunnel - address, port = queue.get() - return process, address, port \ No newline at end of file + while True: + try: + result = queue.get(timeout=1) + except Empty: + if not process.is_alive(): + raise RuntimeError("Tunnel process exited before returning connection info") + continue + + if result.get("status") == "ok": + return process, result["address"], result["port"] + + process.join(timeout=1) + raise RuntimeError(f"Tunnel setup failed: {result.get('error', 'unknown error')}") diff --git a/main.py b/main.py index cb273db..41f1bb5 100644 --- a/main.py +++ b/main.py @@ -2,11 +2,11 @@ import logging import coloredlogs import os +import asyncio -from driver import location - -from pymobiledevice3.cli.remote import RemoteServiceDiscoveryService -from pymobiledevice3.cli.developer import DvtSecureSocketProxyService +from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService +from pymobiledevice3.services.dvt.instruments.dvt_provider import DvtProvider +from pymobiledevice3.services.dvt.instruments.location_simulation import LocationSimulation from init import init from init import tunnel @@ -19,7 +19,6 @@ debug = os.environ.get("DEBUG", False) -# set logging level coloredlogs.install(level=logging.INFO) logging.getLogger('wintun').setLevel(logging.DEBUG if debug else logging.WARNING) logging.getLogger('quic').setLevel(logging.DEBUG if debug else logging.WARNING) @@ -33,9 +32,7 @@ logging.getLogger('urllib3.connectionpool').setLevel(logging.DEBUG if debug else logging.WARNING) - -def main(): - # set level +async def _async_main(): logger = logging.getLogger(__name__) coloredlogs.install(level=logging.INFO) logger.setLevel(logging.INFO) @@ -43,51 +40,56 @@ def main(): logger.setLevel(logging.DEBUG) coloredlogs.install(level=logging.DEBUG) - init.init() + await init.init() logger.info("init done") - # start the tunnel in another process logger.info("starting tunnel") original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) - process, address, port = tunnel.tunnel() - signal.signal(signal.SIGINT, original_sigint_handler) + try: + process, address, port = tunnel.tunnel() + except RuntimeError as exc: + logger.error("failed to start tunnel: %s", exc) + print(f"启动隧道失败:{exc}") + return + finally: + signal.signal(signal.SIGINT, original_sigint_handler) logger.info("tunnel started") try: logger.debug(f"tunnel address: {address}, port: {port}") - # get route loc = route.get_route() logger.info(f"got route from {config.config.routeConfig}") - - with RemoteServiceDiscoveryService((address, port)) as rsd: - with DvtSecureSocketProxyService(rsd) as dvt: - try: - print(f"已开始模拟跑步,速度大约为 {config.config.v} m/s") - print("会无限循环,按 Ctrl+C 退出") - print("请勿直接关闭窗口,否则无法还原正常定位") - run.run(dvt, loc, config.config.v) - except KeyboardInterrupt: - logger.debug("get KeyboardInterrupt (inner)") - logger.debug(f"Is process alive? {process.is_alive()}") - finally: - logger.debug(f"Is process alive? {process.is_alive()}") - logger.debug("Start to clear location") - location.clear_location(dvt) - logger.info("Location cleared") - + async with RemoteServiceDiscoveryService((address, port)) as rsd: + async with DvtProvider(rsd) as dvt: + async with LocationSimulation(dvt) as loc_sim: + try: + print(f"已开始模拟跑步,速度大约为 {config.config.v} m/s") + print("会无限循环,按 Ctrl+C 退出") + print("请勿直接关闭窗口,否则无法还原正常定位") + await run.run(loc_sim, loc, config.config.v) + except KeyboardInterrupt: + logger.debug("get KeyboardInterrupt (inner)") + logger.debug(f"Is process alive? {process.is_alive()}") + finally: + logger.debug(f"Is process alive? {process.is_alive()}") + logger.debug("Start to clear location") + await loc_sim.clear() + logger.info("Location cleared") except KeyboardInterrupt: logger.debug("get KeyboardInterrupt (outer)") finally: - # stop the tunnel process logger.debug(f"Is process alive? {process.is_alive()}") logger.debug("terminating tunnel process") process.terminate() logger.info("tunnel process terminated") print("Bye") - - + +def main(): + asyncio.run(_async_main()) + + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/requirements.txt b/requirements.txt index a35c754..52b7a18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -pymobiledevice3==2.46.1 -PyYAML==6.0.1 -geopy==2.4.1 +pymobiledevice3>=9.12.0 +PyYAML>=6.0 +geopy>=2.4 diff --git a/run.py b/run.py index 917299e..3876855 100644 --- a/run.py +++ b/run.py @@ -7,10 +7,10 @@ import math import time import random +import asyncio from geopy.distance import geodesic -from driver import location def bd09Towgs84(position): wgs_p = {} @@ -57,20 +57,17 @@ def transform_lon(x, y): wgs_p["lng"] = gcj_lng * 2 - gcj_lng - d_lng return wgs_p -# get the ditance according to the latitude and longitude + def geodistance(p1, p2): - return geodesic((p1["lat"],p1["lng"]),(p2["lat"],p2["lng"])).m + return geodesic((p1["lat"], p1["lng"]), (p2["lat"], p2["lng"])).m + def smooth(start, end, i): - import math - i = (i-start)/(end-start)*math.pi - return math.sin(i)**2 + i = (i - start) / (end - start) * math.pi + return math.sin(i) ** 2 + def randLoc(loc: list, d=0.000025, n=5): - import random - import time - import math - # deepcopy loc result = [] for i in loc: result.append(i.copy()) @@ -83,73 +80,77 @@ def randLoc(loc: list, d=0.000025, n=5): center["lng"] /= len(result) random.seed(time.time()) for i in range(n): - start = int(i*len(result)/n) - end = int((i+1)*len(result)/n) - offset = (2*random.random()-1) * d + start = int(i * len(result) / n) + end = int((i + 1) * len(result) / n) + offset = (2 * random.random() - 1) * d for j in range(start, end): distance = math.sqrt( - (result[j]["lat"]-center["lat"])**2 + (result[j]["lng"]-center["lng"])**2 + (result[j]["lat"] - center["lat"]) ** 2 + (result[j]["lng"] - center["lng"]) ** 2 ) if 0 == distance: continue - result[j]["lat"] += (result[j]["lat"]-center["lat"])/distance*offset*smooth(start, end, j) - result[j]["lng"] += (result[j]["lng"]-center["lng"])/distance*offset*smooth(start, end, j) - start = int(i*len(result)/n) + result[j]["lat"] += (result[j]["lat"] - center["lat"]) / distance * offset * smooth(start, end, j) + result[j]["lng"] += (result[j]["lng"] - center["lng"]) / distance * offset * smooth(start, end, j) + start = int(i * len(result) / n) end = len(result) - offset = (2*random.random()-1) * d + offset = (2 * random.random() - 1) * d for j in range(start, end): distance = math.sqrt( - (result[j]["lat"]-center["lat"])**2 + (result[j]["lng"]-center["lng"])**2 + (result[j]["lat"] - center["lat"]) ** 2 + (result[j]["lng"] - center["lng"]) ** 2 ) if 0 == distance: continue - result[j]["lat"] += (result[j]["lat"]-center["lat"])/distance*offset*smooth(start, end, j) - result[j]["lng"] += (result[j]["lng"]-center["lng"])/distance*offset*smooth(start, end, j) + result[j]["lat"] += (result[j]["lat"] - center["lat"]) / distance * offset * smooth(start, end, j) + result[j]["lng"] += (result[j]["lng"] - center["lng"]) / distance * offset * smooth(start, end, j) return result + def fixLockT(loc: list, v, dt): fixedLoc = [] t = 0 T = [] - T.append(geodistance(loc[1],loc[0])/v) + T.append(geodistance(loc[1], loc[0]) / v) a = loc[0].copy() b = loc[1].copy() j = 0 while t < T[0]: - xa = a["lat"] + j*(b["lat"]-a["lat"])/(max(1, int(T[0]/dt))) - xb = a["lng"] + j*(b["lng"]-a["lng"])/(max(1, int(T[0]/dt))) + xa = a["lat"] + j * (b["lat"] - a["lat"]) / (max(1, int(T[0] / dt))) + xb = a["lng"] + j * (b["lng"] - a["lng"]) / (max(1, int(T[0] / dt))) fixedLoc.append({"lat": xa, "lng": xb}) j += 1 t += dt for i in range(1, len(loc)): - T.append(geodistance(loc[(i+1)%len(loc)],loc[i])/v + T[-1]) + T.append(geodistance(loc[(i + 1) % len(loc)], loc[i]) / v + T[-1]) a = loc[i].copy() - b = loc[(i+1)%len(loc)].copy() + b = loc[(i + 1) % len(loc)].copy() j = 0 while t < T[i]: - xa = a["lat"] + j*(b["lat"]-a["lat"])/(max(1, int((T[i]-T[i-1])/dt))) - xb = a["lng"] + j*(b["lng"]-a["lng"])/(max(1, int((T[i]-T[i-1])/dt))) + xa = a["lat"] + j * (b["lat"] - a["lat"]) / (max(1, int((T[i] - T[i - 1]) / dt))) + xb = a["lng"] + j * (b["lng"] - a["lng"]) / (max(1, int((T[i] - T[i - 1]) / dt))) fixedLoc.append({"lat": xa, "lng": xb}) j += 1 t += dt return fixedLoc -def run1(dvt, loc: list, v, dt=0.2): + +async def run1(loc_sim, loc: list, v, dt=0.2): fixedLoc = fixLockT(loc, v, dt) nList = (5, 6, 7, 8, 9) - n = nList[random.randint(0, len(nList)-1)] - fixedLoc = randLoc(fixedLoc, n=n) # a path will be divided into n parts for random route + n = nList[random.randint(0, len(nList) - 1)] + fixedLoc = randLoc(fixedLoc, n=n) clock = time.time() for i in fixedLoc: - # utils.setLoc(bd09Towgs84(i)) - location.set_location(dvt, **bd09Towgs84(i)) - while time.time()-clock < dt: - pass + pos = bd09Towgs84(i) + await loc_sim.set(pos["lat"], pos["lng"]) + elapsed = time.time() - clock + if elapsed < dt: + await asyncio.sleep(dt - elapsed) clock = time.time() -def run(dvt, loc: list, v, d=15): + +async def run(loc_sim, loc: list, v, d=15): random.seed(time.time()) while True: - vRand = 1000/(1000/v-(2*random.random()-1)*d) - run1(dvt, loc, vRand) - print("跑完一圈了") \ No newline at end of file + vRand = 1000 / (1000 / v - (2 * random.random() - 1) * d) + await run1(loc_sim, loc, vRand) + print("跑完一圈了") From f6b6cb138f99d595f398716a60a48bd275df996e Mon Sep 17 00:00:00 2001 From: ImJingLan Date: Wed, 13 May 2026 22:19:01 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=86=E5=AE=9A?= =?UTF-8?q?=E4=BD=8D=E8=BF=98=E5=8E=9F=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 76 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/main.py b/main.py index 41f1bb5..40db037 100644 --- a/main.py +++ b/main.py @@ -31,8 +31,29 @@ logging.getLogger('blib2to3.pgen2.driver').setLevel(logging.DEBUG if debug else logging.WARNING) logging.getLogger('urllib3.connectionpool').setLevel(logging.DEBUG if debug else logging.WARNING) +_cleanup_address = None +_cleanup_port = None +_cleanup_process = None + + +async def _do_clear_location(): + if _cleanup_address is None: + return + try: + async with RemoteServiceDiscoveryService((_cleanup_address, _cleanup_port)) as rsd: + async with DvtProvider(rsd) as dvt: + async with LocationSimulation(dvt) as loc: + await asyncio.wait_for(loc.clear(), timeout=5.0) + print("定位已清除") + except asyncio.TimeoutError: + print("清除定位超时,请重启手机恢复") + except Exception as e: + print(f"清除定位时出错: {e},请重启手机恢复") + async def _async_main(): + global _cleanup_address, _cleanup_port, _cleanup_process + logger = logging.getLogger(__name__) coloredlogs.install(level=logging.INFO) logger.setLevel(logging.INFO) @@ -46,7 +67,7 @@ async def _async_main(): logger.info("starting tunnel") original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) try: - process, address, port = tunnel.tunnel() + _cleanup_process, _cleanup_address, _cleanup_port = tunnel.tunnel() except RuntimeError as exc: logger.error("failed to start tunnel: %s", exc) print(f"启动隧道失败:{exc}") @@ -54,41 +75,36 @@ async def _async_main(): finally: signal.signal(signal.SIGINT, original_sigint_handler) logger.info("tunnel started") - try: - logger.debug(f"tunnel address: {address}, port: {port}") - loc = route.get_route() - logger.info(f"got route from {config.config.routeConfig}") + logger.debug(f"tunnel address: {_cleanup_address}, port: {_cleanup_port}") - async with RemoteServiceDiscoveryService((address, port)) as rsd: - async with DvtProvider(rsd) as dvt: - async with LocationSimulation(dvt) as loc_sim: - try: - print(f"已开始模拟跑步,速度大约为 {config.config.v} m/s") - print("会无限循环,按 Ctrl+C 退出") - print("请勿直接关闭窗口,否则无法还原正常定位") - await run.run(loc_sim, loc, config.config.v) - except KeyboardInterrupt: - logger.debug("get KeyboardInterrupt (inner)") - logger.debug(f"Is process alive? {process.is_alive()}") - finally: - logger.debug(f"Is process alive? {process.is_alive()}") - logger.debug("Start to clear location") - await loc_sim.clear() - logger.info("Location cleared") + loc = route.get_route() + logger.info(f"got route from {config.config.routeConfig}") - except KeyboardInterrupt: - logger.debug("get KeyboardInterrupt (outer)") - finally: - logger.debug(f"Is process alive? {process.is_alive()}") - logger.debug("terminating tunnel process") - process.terminate() - logger.info("tunnel process terminated") - print("Bye") + async with RemoteServiceDiscoveryService((_cleanup_address, _cleanup_port)) as rsd: + async with DvtProvider(rsd) as dvt: + async with LocationSimulation(dvt) as loc_sim: + print(f"已开始模拟跑步,速度大约为 {config.config.v} m/s") + print("会无限循环,按 Ctrl+C 退出") + print("请勿直接关闭窗口,否则无法还原正常定位") + await run.run(loc_sim, loc, config.config.v) def main(): - asyncio.run(_async_main()) + try: + asyncio.run(_async_main()) + except KeyboardInterrupt: + pass + + if _cleanup_process is not None: + print("\n正在清除定位...") + try: + asyncio.run(_do_clear_location()) + except Exception: + pass + _cleanup_process.terminate() + _cleanup_process.join(timeout=5) + print("Bye") if __name__ == "__main__": From d105ba0bb36f9fc7210b005d5c4c7843ca6bcb08 Mon Sep 17 00:00:00 2001 From: ImJingLan Date: Wed, 13 May 2026 22:29:31 +0800 Subject: [PATCH 3/3] docs: update README --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de19449..c95e2f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# iOSRealRun-cli-17 +# iOSLocationSimulator-cli-18 ## 用法简介 @@ -42,3 +42,14 @@ - 依赖库升级至 pymobiledevice3 >= 9.12.0 - DVT 连接使用新的 `DvtProvider` / `LocationSimulation` 异步 API - 完整复现原有模拟定位功能(坐标系转换、路径插值、随机扰动等) + + +### 声明 + + 1. 此软件旨在帮助开发者等专业人士进行软件开发,游戏开发,功能调试,应用兼容性测试等位置信息相关功能的开发调试工具。 + + 2. 用户(下称“您”)需要确保不得通过本软件以任何方式进行非法、欺诈、侵犯第三方权益、违反其他应用服务条款的行为,包括但不限于用于办公打卡、签到、网约车、校园跑、配送服务相关的行为。我们保留向监管部门举报您违法违规行为的权利。 + + 3. 您知悉并同意本软件仅限您用于合法的软件开发、功能调试、应用兼容性测试等专业目的。并且未经我们同意,您不得基于本软件进一步对外提供服务。 + + 4. 受限于技术能力,我们暂时无法主动识别并对已知的、易被用于非法、欺诈、侵犯第三方权益或违反其他应用服务条款的高风险应用(APP),采取技术措施进行功能作用屏蔽,您不得尝试绕过或破坏我们的屏蔽措施,否则将承担本协议第2条所述的严重违约责任。 \ No newline at end of file