Files
ios-location-track/docs/WINDOWS_PORT.md
T
jbjf6666 4d55e2415f feat(windows): 新增 Windows 移植(免管理员隧道 + 自动装 Apple 驱动 + 环境自检)
按 docs/WINDOWS_PORT.md 完成 Windows 移植,并修正该文档中若干与实测不符之处。
macOS 构建链完整保留,单一代码库双平台构建。

核心:iOS 17+ RSD 隧道在 Windows 上换了实现方式
- macOS 借 Apple remoted(--native,内核可路由),Windows 改走 UserspaceRsdTunnel
  (纯 Python PyTCP 用户态协议栈)
- 实测:不需要管理员权限、不需要 Wintun 驱动(pytun_pmd3 自带 wintun.dll)、
  建立耗时约 0.3 秒
- 关键坑:用户态隧道的设备地址**只在本进程可达**。拿 (host, port) 另建 RSD 会得到
  OSError [WinError 1231] 网络位置不可达。必须把 aopen() 返回的 rsd 对象本身注入
  DVT 层(RealLocationProvider 早有 rsd= 注入口),因此 Windows 不能沿用
  「隧道助手子进程 + 解析 HOST PORT」那套架构
- 修正 WINDOWS_PORT.md 的错误:类名是 UserspaceRsdTunnel 不是 UserspaceTunnel;
  remote start-tunnel 没有 --userspace;Wintun 不是必需项

新增
- core/platform.py      平台抽象层(数据目录/浏览器探测/进程管理/提权/信号)
- core/apple_driver.py  Apple Mobile Device Support 检测与自动安装
                        (服务启动 → 独立 AMDS MSI → 完整 iTunes 包 → winget 逐级降级)
                        + 开发者镜像下载
- core/setup_window.py  启动时的「环境准备」窗口:逐项列出依赖,缺失项右侧可点安装,
                        必需项全部就绪后「进入程序」才可点击
- scripts/run.ps1, build.ps1, installer/iosloc.iss, installer/version_info.txt
- tools/make_ico.py(由已有 PNG 图标集生成多尺寸 .ico)
- tools/probe_tunnel_win.py(隧道可行性诊断)
- tests/win_e2e.py(7 项)、tests/win_app_e2e.py(9 项)真机验收
- docs/README-WINDOWS.md
- .gitattributes(避免 Windows 写出 CRLF 的 .sh 把 macOS 构建搞坏)

修改
- core/tunnel.py            Windows 走进程内 userspace 隧道;macOS 路径不变
- core/server.py            TunnelManager 注入 runner;provider_for 注入 rsd 对象;
                            资源目录与数据目录分离;日志落盘
- core/location_provider.py 平台化错误提示;新增保活(隧道静默断开会让设备
                            偷偷恢复真实定位,停止播放时每 15s 重发)
- main.py                   「环境准备」窗口、Apple 驱动后台检查、
                            --check-driver / --install-driver、跨平台浏览器与信号
- Location.spec             平台分支;Windows 用 .ico + uac_admin=True(启动即请求管理员)
                            + version_info.txt;单一 spec 不分叉
- tests/drift_sim.py / offline_tiles.py  去掉硬编码 /tmp,改用 tempfile.gettempdir()

打包
- exe 清单写入 requireAdministrator(用户要求:启动时直接请求管理员权限)
- 绿色版 dist\iOSLocationTrack\iOSLocationTrack.exe
- 安装包 dist\installer\iOSLocationTrack-1.0.0-setup.exe(Inno Setup)

实测(Windows 11 x64 / Python 3.12.14 / pymobiledevice3 11.31.0 / iPhone 11 iOS 26.6.1)
- 隧道→镜像→DVT→下发定位→恢复真实定位 全链路跑通,连接 0.8s
- selftest 83/83、drift_sim 40/40、offline_tiles 13/13
- win_e2e 7/7、win_app_e2e 9/9
- 打包版 exe:发现设备 → 0.3s 隧道 → DVT → 两次下发 → 恢复真实定位 → 干净退出
2026-10-16 01:41:59 +08:00

23 KiB
Raw Permalink Blame History

Windows 移植交接文档

本文档由 macOS 侧的开发会话产出,用于把「iOS 虚拟定位轨迹工具」移植到 Windows。 仓库:https://github.com/jbjf6666/ios-location-track (macOS 版已发布,v1.0.0)

给 Windows 会话的建议:先完整读一遍本文档,再动手。最大的风险点(iOS 17+ 隧道) 集中在第 3 节,请先做那里的可行性验证再决定整体架构——不要先写界面。


0. 一句话结论

约 90% 的代码可以直接复用(纯 Python + 纯前端,零平台依赖)。 真正需要重写的只有两处:iOS 17+ 的隧道建立方式(macOS 用 Apple 私有框架,Windows 必须换方案) 和打包/分发链路(DMG → 安装包)。窗口启动方式也需改。

总计约 70 行平台相关代码需要替换,其余 6000+ 行可原样移植。


1. 项目结构与可移植性对照表

文件 行数 可移植性 说明
web/ (index.html / style.css / js/*) ~2500 ✅ 完全可移植 纯前端,零改动。自绘 Canvas 地图,无第三方依赖
core/<br>playback.py 687 ✅ 完全可移植 播放引擎(弧长积分、速度剖面、seek、循环)
core/route_engine.py 576 ✅ 完全可移植 路线规划(OSRM + 直线退化、途经点)
core/geoutils.py 234 ✅ 完全可移植 坐标换算(WGS84 / GCJ-02、haversine、抽稀)
core/noise.py 208 ✅ 完全可移植 真实感模型(多尺度值噪声、漂移、抖动、粗差)
core/config_store.py 312 ✅ 完全可移植 配置持久化(路径需调整)
core/ws.py 326 ✅ 完全可移植 手写 WebSocket(RFC 6455),基于 stdlib
core/tilecache.py 323 ✅ 完全可移植 瓦片代理 + 磁盘 LRU 缓存
core/protocol.py 160 ✅ 完全可移植 WS 协议常量与错误类型
core/location_mock.py 229 ✅ 完全可移植 无设备模拟模式
core/device.py 427 ⚠️ 基本可移植 设备发现用 pymobiledevice3.usbmux,Windows 上走 Apple Mobile Device 服务(iTunes 组件)
core/location_provider.py 701 ⚠️ 基本可移植 DVT 会话逻辑跨平台;只有 1 处 macOS 特定(错误提示文案里的 ./run.sh)
core/server.py 1352 ⚠️ 少量改动 HTTP + WS 服务、命令分发;约 15 处平台相关(窗口/路径/提示)
core/tunnel.py 434 ❌ 需重写 隧道建立,见第 3 节
main.py 284 ⚠️ 需改 窗口启动(Chrome/Edge 路径)、数据目录
tools/make_dmg.py — ❌ 纯 macOS 用 hdiutil,Windows 需换安装包方案
tools/make_icon.py — ✅ 可移植 Pillow 绘制,但 .icns 生成用 iconutil(macOS)→ Windows 需改成 .ico
tools/verify_bundle.py — ⚠️ 需改 校验逻辑通用,但 codesign / .app 结构是 macOS 的
Location.spec — ⚠️ 需改 PyInstaller 配置,Windows 要改成 .exe + BUNDLE → 无(改用 Inno Setup / NSIS)
tests/* ~2000 ✅ 大部分可移植 selftest.py / drift_sim.py 纯逻辑,跨平台;ui_acceptance.py 需改浏览器路径
resources/ddi/ 16 MB ✅ 可移植 开发者镜像素材,与平台无关

2. Windows 环境准备

1. Python 3.11+(建议 3.12,与 macOS 侧一致)
2. iTunes(或至少 Apple Mobile Device Support)—— 提供 usbmuxd 等价服务,
   pymobiledevice3 依赖它才能通过 USB 发现 iOS 设备
   下载:Microsoft Store 版 iTunes,或 Apple 官网的 iTunes for Windows
3. Wintun 驱动(用于 userspace 隧道,见第 3 节)
   https://www.wintun.net/
4. Git

依赖安装:

python -m venv .venv
.venv\Scripts\activate
pip install pymobiledevice3 pillow pyinstaller

注意:macOS 侧 scripts/run.sh 里写了国内 PyPI 镜像 mirrors.aliyun.com, Windows 侧可沿用(pip.conf 里已有配置,Windows 下需放到 %APPDATA%\pip\pip.ini)。


3. 核心难点:iOS 17+ 的 RSD 隧道(必须先验证)

3.1 为什么 macOS 的实现不能直接用

iOS 17 起,虚拟定位不再走 usbmux,必须先建立 RSD 隧道。macOS 侧走的是 pymobiledevice3.remote.native_tunnel,其实现完全建立在 Apple 私有框架上:

_IS_DARWIN = platform.system() == "Darwin"
lib = ctypes.CDLL("/usr/lib/libSystem.B.dylib", use_errno=True)
# 然后调用 xpc_connection_create_mach_service、xpc_dictionary_* 等
# 以及 ObjC block:_NSConcreteGlobalBlock

这些 API 是 macOS 独有的(XPC + Objective-C runtime),Windows 上不存在。 pymobiledevice3 的 establish_native_rsd() 在非 macOS 平台会抛 UserspaceTunnelUnavailableError。

3.2 Windows 上的可行方案:userspace tunnel

pymobiledevice3 内置了跨平台的 userspace 隧道实现,Windows 是明确支持的目标平台:

from pymobiledevice3.remote import userspace_tunnel
from pymobiledevice3.remote import tunnel_service

# 关键开关
tunnel_service.USE_USERSPACE_TUNNEL = True

实现要点(读源码可知):

  • 使用 pytun_pmd3.TunTapDevice 创建虚拟网卡(Windows 上底层是 Wintun 驱动)
  • macOS/Linux 用 AF_UNIX socketpair;Windows 没有 socketpair,改用 TUN 设备直连
  • 该包已在依赖列表中(pytun_pmd3-3.0.3),无需额外安装

3.3 验证步骤(请先做完这一步再写别的代码)

重要:--userspace 不是 remote start-tunnel 的选项(该命令只有 --native)。 实测确认它挂在共享的 RSD 依赖上,lockdown start-tunnel 与 mounter auto-mount 才接受它:

命令 --userspace
lockdown start-tunnel ✅ 支持
mounter auto-mount ✅ 支持
remote start-tunnel ❌ 不支持(只有 --native,且 --native 是 macOS 专用)
developer dvt simulate-location ❌ 不支持
# 1) 设备能否被发现(需要 iTunes 提供的 Apple Mobile Device 服务)
python -m pymobiledevice3 usbmux list

# 2) 建立 userspace 隧道(Windows 上的正解)
python -m pymobiledevice3 lockdown start-tunnel --userspace --script-mode

期望输出形如 HOST PORT(例如 fd00:...:1 54321)—— 这正是 macOS 侧 core/tunnel.py:_read_output() 解析的格式,保持这个格式,上层代码就不用改。

底层机制(读源码确认):userspace_tunnel.UserspaceTunnel.aopen() 内部会设置 tunnel_service.USE_USERSPACE_TUNNEL = True,从而让 RemotePairingTunnel.start_tunnel() 通过 create_tun_device() 工厂拿到 UserspaceTun(底层是 pytun_pmd3.TunTapDevice → Windows 上是 Wintun),而不是内核隧道。所以不需要任何 monkeypatch,用上面的命令即可。

限制:PyTCP 的协议栈是进程内全局单例,一个进程同时只能有一条 userspace 隧道 (代码里会抛 "a userspace tunnel is already active in this process")。 这对本工具没影响——播放引擎本来就是单一长连接,但要记得别在进程里重复建隧道。

必须确认的事项:

检查项 为什么重要
是否需要管理员权限 Wintun 装驱动/建网卡通常需要;这决定要不要在界面上加提权按钮
Windows 防火墙是否弹窗 首次建立隧道可能弹窗,需要写进文档
隧道建立后 ping6 是否通 验证虚拟网卡真的工作了
断开后网卡是否残留 残留会导致下次建立失败,需要清理逻辑

3.4 隧道模块的重写接口

core/tunnel.py 的 TunnelManager 类已经把隧道抽象好了,保持以下公开接口不变, 只替换内部实现即可,上层(server.py / location_provider.py)无需改动:

class TunnelManager:
    def ready(self) -> bool: ...                    # 隧道是否就绪
    def address(self) -> tuple[str, int] | None: ... # (host, port)
    def ensure(self, udid: str | None, allow_sudo: bool = False) -> dict: ...
    def start(self, udid: str | None = None, allow_sudo: bool = False) -> dict: ...
    def use_manual(self, host: str, port: int, udid: str | None = None) -> dict: ...
    def stop(self) -> None: ...
    def snapshot(self) -> dict: ...                 # 状态快照,推给前端

建议的实现方式:照抄现有结构,把 _build_command() 换成:

if sys.platform == "win32":
    # 注意:不是 remote start-tunnel,那个命令没有 --userspace
    base = [sys.executable, "-m", "pymobiledevice3",
            "lockdown", "start-tunnel", "--userspace", "--script-mode"]

打包后(sys.frozen)同样走自调用:

base = [sys.executable, "tunnel-helper"]     # exe 调用自己的子命令

并在 tunnel-helper 内部用 Python API 建立 userspace 隧道:

# 注意类名是 UserspaceRsdTunnel(不是 UserspaceTunnel)
from pymobiledevice3.remote.userspace_tunnel import UserspaceRsdTunnel

tunnel = UserspaceRsdTunnel(serial=udid, autopair=True, remotepairing_fallback=True)
rsd = await tunnel.aopen()                    # -> RemoteServiceDiscoveryService
host, port = rsd.service.address
print(f"{host} {port}", flush=True)           # 保持这个格式,上层无需改动

# 进程退出前释放(否则虚拟网卡可能残留)
await tunnel.aclose()                         # 或 tunnel.close()

另外提供同步接口 open() / close(),以及 UserspaceTun(TUN 设备本身, 一般不需要直接用它)。

重要经验:macOS 侧的 tunnel-helper 是「自调用」模式(打包后的 exe 调用自己 的 tunnel-helper 子命令),这样打包后不依赖外部 Python。Windows 侧建议照做。 实现见 main.py:run_tunnel_helper() 与 core/tunnel.py:_build_command()。


4. 其余需要改动的部分

4.1 窗口启动(main.py)

macOS 侧用 Chrome/Edge 的 --app= 模式打开无地址栏窗口:

# macOS 路径
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"

Windows 需改为:

CANDIDATES = [
    r"C:\Program Files\Google\Chrome\Application\chrome.exe",
    r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
    r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
    r"C:\Program Files\Microsoft\Edge\Application\msedge.exe",
]
# 或从注册表读:
# HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe

--app={url} --window-size=1440,900 参数在 Windows 上同样有效。

另一个选择:用 pywebview(Windows 上走 Edge WebView2)做成真正的原生窗口, 体验更接近 macOS 版。代价是多一个依赖。

4.2 数据目录(main.py:_resolve_roots())

# macOS: ~/Library/Application Support/iOSLocationTrack
# Windows 应改为:%LOCALAPPDATA%\iOSLocationTrack
os.environ.get("LOCALAPPDATA", os.path.expanduser("~"))

4.3 打包与分发

macOS Windows 对应
PyInstaller --windowed + .app bundle PyInstaller --windowed 生成 .exe(onedir 更稳)
codesign --sign -(ad-hoc 签名) 无对应;未签名 exe 会有 SmartScreen 警告
hdiutil 制作 DMG Inno Setup(推荐,免费)或 NSIS 制作 setup.exe
iconutil 生成 .icns Pillow 直接存 .ico(多尺寸:16/32/48/64/128/256)

tools/make_icon.py 的绘制代码可完全复用,只需把最后的 .icns 输出改成 .ico:

sizes = [16, 24, 32, 48, 64, 128, 256]
img.save("app.ico", sizes=[(s, s) for s in sizes])

4.4 需要删除或替换的文件

  • tools/make_dmg.py —— 纯 macOS,直接删掉
  • tools/ddi_offline_fetch.sh —— bash 脚本,Windows 无用
  • scripts/run.sh / scripts/build_app.sh —— 改写为 .ps1 或 .bat
  • Location.spec —— 改写:去掉 BUNDLE(),icon 改成 .ico,console=False

5. 设备侧注意事项(平台无关,但容易踩坑)

这些是 macOS 侧实测踩过的坑,Windows 上同样会遇到:

5.1 镜像挂载用 Python API,不要用 CLI

打包成 exe 后没有 pymobiledevice3 命令行入口,必须用 Python API:

from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.services.mobile_image_mounter import (
    MobileImageMounterService, auto_mount)

client = await create_using_usbmux(serial=udid, autopair=True,
                                   connection_type="USB", pair_timeout=5.0)
async with MobileImageMounterService(lockdown=client) as mounter:
    for image_type in ("Personalized", "Developer"):
        if await mounter.is_image_mounted(image_type):
            return  # 已挂载,直接返回(否则会抛 AlreadyMountedError)
await auto_mount(client, version=version)

踩坑记录:

  • AlreadyMountedError 必须当成功处理,不是失败
  • 不要只检查「镜像是否挂载」就认为可用——macOS 侧实测 iOS 16.5 上镜像显示已挂载但 DVT 服务不启动
  • iOS 17+ 不要用 lockdown 服务名探测 DVT(com.apple.instruments.dtservicehub 走 RSD,不在 lockdown 里)

5.2 必须打包包元数据

PyInstaller 默认不打包 dist-info,而部分包(如 apple_compress)在运行期用 importlib.metadata 读自己的版本,漏掉会报 No package metadata was found for apple_compress,导致镜像挂载失败:

from PyInstaller.utils.hooks import copy_metadata
datas = []
for pkg in ("apple_compress", "pymobiledevice3", "pyimg4", "construct",
            "cryptography", "bpylist2", "opack2"):
    try:
        datas += copy_metadata(pkg)
    except Exception:
        pass

5.3 不要把 distutils 加进 excludes

PyInstaller 的 hook 会强制导入它,排除后构建直接失败:

ValueError: Target module "distutils" already imported as "ExcludedModule('distutils',)"

同理 questionary / prompt_toolkit / pygments 被 pymobiledevice3 运行期导入, 排除后真机功能会静默失效(设备发现退化成 libimobiledevice 兜底)。

5.4 支持的 iOS 版本范围

iOS 17 ~ 27.2,一套个性化镜像全系共用(resources/ddi/personalized/,约 16 MB)。 挂载时由 Apple TSS 在线签发,因此只有签名步骤需要联网。

iOS 16 及更早不支持——其开发者镜像必须由 Apple 按系统构建号签发, 第三方镜像会被设备拒签(已实测:Failed to verify the disk image signature, 换 libimobiledevice 同样失败,说明是设备端拒绝)。


6. 术语与设计约束(改代码前必读)

约束 原因
DVT 连接必须长连接保持 会话一断,模拟定位立即失效。所以播放引擎不能每发一个点就重连
DVT 只能改经纬度 simulateLocationWithLatitude:longitude: 仅两个参数。horizontalAccuracy、altitude、floor、isSimulatedBySoftware、加速度计/陀螺仪/计步器由 iOS 合成或来自硬件,无法注入
last_point 与 last_reported 必须分开 前者是轨迹上的当前点(供 status 用),后者是实际下发的点(用于推算速度)。混用会导致播放线程崩溃或速度算错
速度推算要用单调时钟 macOS 侧曾因用 time.time() 且读取顺序错误,导致 dt 恒被钳到下限、速度偏大 20 倍
update_tuning 的参数默认值必须是哨兵 用 None 表示「不修改」会导致 False 也被当成「不修改」,开关永远关不掉
坐标内部一律 WGS84 高德瓦片是 GCJ-02,只在前端请求瓦片与落点时转换
同一时间只能有一个工具占隧道 设备只有一条 RSD 连接,i4Tools / Xcode / 本工具会互相挤掉

7. 验收标准(移植完成后请逐项跑通)

macOS 侧的测试套件大部分可直接复用,作为 Windows 版的验收基线:

python tests\selftest.py          # 83 项:协议、几何、播放引擎、配置、瓦片(无需设备)
python tests\drift_sim.py         # 40 项:真实感模型(幅度、平滑性、开关、参数校验)
python tests\offline_tiles.py     # 13 项:瓦片缓存
python main.py --mock             # 界面能开、能画轨迹、能播放(无设备)

真机验收(这一项是 Windows 版的核心风险点):

  1. pymobiledevice3 usbmux list 能看到设备
  2. 点「挂载开发者镜像」成功
  3. 点「连接设备」→ 隧道建立 → DVT 会话就绪
  4. 播放轨迹 → 手机定位真的移动
  5. 点「停止」→ 手机恢复真实定位

8. 建议的 Windows 侧工作顺序

第 1 步:环境 + 可行性验证(最高优先级,1~2 小时)
  └─ 装 iTunes / pymobiledevice3 / Wintun
  └─ 跑通 `remote start-tunnel --userspace`,拿到 HOST PORT
  └─ 用 pymobiledevice3 的 DVT 直接下发一次定位,确认手机真的动了
  ★ 这一步不通,后面全白做。务必先做。

第 2 步:移植纯逻辑层(无风险,可并行)
  └─ 复制 core/ 下 10 个可移植模块 + web/ 整个目录
  └─ 跑 tests/selftest.py 与 tests/drift_sim.py 确认 123 项全绿

第 3 步:重写隧道层
  └─ 按第 3.4 节的接口实现 Windows 版 TunnelManager
  └─ 实现 `--tunnel-helper` 自调用模式(为打包做准备)

第 4 步:窗口与路径适配
  └─ main.py 的浏览器路径、数据目录

第 5 步:打包
  └─ PyInstaller → .exe(注意第 5.2、5.3 节的坑)
  └─ Inno Setup 制作安装包

第 6 步:完整验收(第 7 节)

9. 需要向 macOS 侧(或用户)确认的问题

  1. 目标 Windows 版本?(Windows 10 / 11,是否要支持 ARM64)
  2. 是否需要内嵌浏览器(pywebview + WebView2)还是沿用 Chrome/Edge --app= 模式?
  3. 安装包形式:Inno Setup setup.exe / 绿色免安装 zip?
  4. 隧道方案确认:如果 --userspace 在目标机器上不通,备选方案是 让用户手动跑 tunneld(走「手动 RSD」入口,界面已支持),需要确认可接受性
  5. 是否要保留 macOS 与 Windows 双平台同一代码库? 建议:把平台相关代码集中到 core/platform_win.py / core/platform_mac.py, 用一个 core/platform.py 分发,避免分叉两份代码

10. 参考


11. ⚠️ 移植完成后的修正(先读这一节)

以下是 Windows 侧实际动手后发现本文档有误的地方。上文保留原样以便对照, 但以本节为准。详细说明见 README-WINDOWS.md。

# 本文档原文 实际情况
1 §3.3 说 userspace_tunnel.UserspaceTunnel.aopen() 类名不存在,正确是 UserspaceRsdTunnel。§3.4 已改对,§3.3 的正文漏改。
2 §8 第 1 步让跑 remote start-tunnel --userspace 该命令没有 --userspace 选项。实测 lockdown start-tunnel --userspace 在 Windows 上会以 Connection was terminated abruptly 失败。正解是 Python API:UserspaceRsdTunnel(...).aopen()。
3 §2 / §8 把 Wintun 驱动列为必需前置 完全不需要。userspace 隧道走纯 Python PyTCP 协议栈,不建内核网卡;而且 pytun_pmd3 依赖里自带 wintun/bin/amd64/wintun.dll。无需下载、无需安装、无需管理员权限。
4 §3.3 问「是否需要管理员权限 / 防火墙弹窗 / 网卡残留」 实测:都不需要。免管理员、无防火墙弹窗、无虚拟网卡(因此也没有残留问题)。隧道建立约 0.3 秒。
5 §3.4 说「保持公开接口不变,只替换 _build_command()」 不够。core/tunnel.py 还有一批 POSIX-only 代码:os.killpg/os.getpgid、start_new_session=True、ps -p、osascript、.venv/bin/python、~/Library/Application Support。这些已全部收进 core/platform.py。
6 §3.4 建议 Windows 沿用「隧道助手子进程 + 解析 HOST PORT」 架构上行不通。用户态隧道的设备地址只在建立它的进程里可达;拿 (host, port) 另建 RSD 会得到 OSError [WinError 1231] 网络位置不可达。必须把 aopen() 返回的 rsd 对象本身注入 DVT 层。
7 §5.3 只提到别排除 distutils wheel 同理:Windows + setuptools 84 上会报 ValueError: Target module "wheel" already imported as ExcludedModule('wheel',)。setuptools / pip 也建议一并保留。
8 §1 说 drift_sim.py 等「纯逻辑,跨平台」 drift_sim.py 与 offline_tiles.py 硬编码了 /tmp/...。Windows 上虽然会落到 C:\tmp(有权限时能跑),但已改为 tempfile.gettempdir()。
9 §1 称 resources/ddi/ 是「16 MB 可移植」 实际入库的只有 BuildManifest.plist + 两个 trustcache(约 0.8 MB);Image.dmg(15.7 MB)被 .gitignore 排除,新克隆必须跑 tools/fetch_ddi.py。已加进 scripts\run.ps1 / build.ps1 自动补齐。
10 §4.4 建议「删掉 tools/make_dmg.py、把 run.sh 改写成 .ps1」 没有删。改成新增 run.ps1 / build.ps1 + Location.spec 平台分支,单一份代码库两个平台都能构建,避免分叉(§9.5 的目标)。

实测环境与结果

Windows 11 x64(10.0.26200)· Python 3.12.14 · pymobiledevice3 11.31.0 · iPhone 11(iPhone12,1)iOS 26.6.1 · AMDS 20.0.0.35 · iTunes 12.13.11.1

  • 隧道 → 镜像 → DVT → 下发定位 → 恢复真实定位:全链路跑通,连接耗时 0.8 秒
  • tests/selftest.py 83/83、drift_sim.py 40/40、offline_tiles.py 13/13
  • 新增真机验收:tests/win_e2e.py 7/7、tests/win_app_e2e.py 9/9
  • 打包版 exe 实测:发现设备 → 0.3s 隧道 → DVT → 两次下发 → 恢复真实定位 → 干净退出

新增的 Windows 专属能力(本文档未提及)

  1. 「环境准备」窗口(每次启动弹出):逐项列出依赖,缺失项右侧有「安装」按钮, 必需项全部就绪后「进入程序」才可点。
  2. 自动安装 Apple Mobile Device Support:服务启动 → 独立 AMDS MSI → 完整 iTunes 包 → winget, 逐级降级;成功码 0/3010/1641/1638。
  3. exe 清单写入 requireAdministrator(Location.spec 的 uac_admin=True),启动即提权。
  4. 保活机制:隧道会静默断开并让设备偷偷恢复真实定位,因此设了点但停止播放时 每 15 秒重发一次(core/location_provider.py:KEEPALIVE_INTERVAL)。
  5. 日志落盘:窗口程序没有控制台,日志写到 %LOCALAPPDATA%\iOSLocationTrack\logs\app.log。