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

462 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```
依赖安装:
```powershell
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 私有框架上**:
```python
_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 是**明确支持的目标平台**:
```python
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` | ❌ 不支持 |
```powershell
# 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`)无需改动:
```python
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()` 换成:
```python
if sys.platform == "win32":
# 注意:不是 remote start-tunnel,那个命令没有 --userspace
base = [sys.executable, "-m", "pymobiledevice3",
"lockdown", "start-tunnel", "--userspace", "--script-mode"]
```
打包后(`sys.frozen`)同样走自调用:
```python
base = [sys.executable, "tunnel-helper"] # exe 调用自己的子命令
```
并在 `tunnel-helper` 内部用 Python API 建立 userspace 隧道:
```python
# 注意类名是 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=` 模式打开无地址栏窗口:
```python
# macOS 路径
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
```
Windows 需改为:
```python
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()`)
```python
# 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`:
```python
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:
```python
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`,导致镜像挂载失败:
```python
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 版的验收基线:
```powershell
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. 参考
- 仓库:https://github.com/jbjf6666/ios-location-track
- 协议契约:`docs/PROTOCOL.md`
- 设备侧取证记录:`docs/DEVICE_NOTES.md`(含 macOS 侧实测的完整排障过程)
- pymobiledevice3 文档:https://github.com/doronz88/pymobiledevice3
- userspace 隧道源码:`pymobiledevice3/remote/userspace_tunnel.py`
- 开发者镜像素材来源:https://github.com/doronz88/DeveloperDiskImage
---
## 11. ⚠️ 移植完成后的修正(**先读这一节**)
以下是 Windows 侧**实际动手后**发现本文档有误的地方。上文保留原样以便对照,
但**以本节为准**。详细说明见 [`README-WINDOWS.md`](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`。