Files

177 lines
8.8 KiB
Markdown
Raw Permalink 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.
# v1.1.1 变更清单(Windows)
> 本次只在 Windows 端更新。macOS 端尚未重新构建,等你更新 mac 后一起发布。
> 版本号 `1.1.0` → `1.1.1`(bug 修复 + 交互改进)。
---
## 一、修复的 Bug
### 严重(会导致功能错误 / 给错设备下发定位)
**1. 切换设备时复用了上一台设备的 RSD → 给错误的手机下发定位**
`core/server.py: provider_for()`
原逻辑只在「没有任何隧道」时才调 `tunnel.ensure(device.udid)`。如果先连 A 再选 B,
隧道状态仍是 connected,于是 B 的 provider 拿到的是 **A 的 RSD 对象** ——
DVT 流量全发到 A,而界面显示的是 B,没有任何地方会报错。
修复:无条件调用 `tunnel.ensure(device.udid)`,由它内部比对 UDID 并决定是否重建。
**2. `tunnel.stop()` 竞态:释放期间 `ready` 仍为 True 但 `rsd_object()` 已是 None**
`core/tunnel.py: stop()`
原逻辑先清 `_rsd`,最后才清 `host/port/state`。中间那段时间(最长 25 秒)并发的
`provider_for` 会走到 `address()` 回退分支,拿用户态隧道地址去做**内核 TCP 连接**,
必然报 `OSError [WinError 1231] 网络位置不可达`。
修复:在锁内一次性把 state / host / port / rsd 全部清掉再释放资源。
另外新增 `invalidate(reason)`,用于把隧道标记为失效但不释放资源。
**3. 隧道静默断开后仍然报告 connected,持续注入已死的 RSD**
`core/tunnel.py: ready`
pymobiledevice3 的 `UserspaceRsdTunnel` 在 USB 拔出/传输中断时会自己 `aclose()`
并把内部 `rsd` 置空,但 `TunnelManager` 还留着旧引用。表现为界面显示「已连接」,
但怎么操作都不生效,只能手动断开重连。
修复:`ready` 增加健康检查 —— `self._userspace_tunnel.rsd is None` 时判定为不可用,
下一次 `ensure()` 会重建隧道。新增 `is_alive()` 供上层判断。
**4. AMDS 已装好、只是 27015 探针失败时,会去下 193MB 的 iTunes**
`core/apple_driver.py: ensure()`
服务装着且在运行、但 usbmuxd 协议往返失败时,原逻辑直接进入安装阶梯:
先跑 38MB 的 AMDS MSI(返回 1638「已安装」被当成成功),复检仍不通,
于是继续下载 193MB 的完整 iTunes 包,再试 winget。整个过程纯属浪费。
修复:**服务存在时先重启服务**(最多两次,带退避),成功即返回;
仍不通则给出明确的排查建议(防火墙/安全软件拦截 127.0.0.1:27015、重插数据线),
而不是盲目重装。新增 `restart_service()`。
### 中等
**5. `clear_location()` 之后保活任务被永久停掉**
`core/location_provider.py`
`clear_location()` 里调了 `_stop_keepalive()`,之后再次 `set_location` 也**不会重启**
保活任务 —— 等于「设了点但暂停/静止」这个最需要保活的场景彻底失去保护,
而保活正是 Windows 用户态隧道防「静默断线导致手机恢复真实定位」的关键。
修复:`clear_location` 不再停保活(循环本来就会在 `_last_location is None` 时空转);
`set_location` 幂等地重新拉起任务兜底。
**6. DDI 暂存选错目录 → 可能拷进残缺素材,挂载必失败**
`core/server.py: _stage_personalized_ddi()`
原逻辑「找到第一个有 `Image.dmg` 的目录就用」,但只有三件齐全(Image.dmg /
BuildManifest.plist / Image.dmg.trustcache)才能挂载。如果打包内置目录缺 trustcache,
会拷一份残缺素材进缓存,然后挂载失败。
修复:只挑选**三件齐全**的目录(与 `apple_driver.ddi_status()` 判定一致),
拷贝后再校验一次落地完整性,缺任何一件就明确报错并返回 False。
**7. 「进入程序」被可选项拖住 / 「管理员权限」在不需要时也阻塞**
`core/setup_window.py`
- 底部按钮原本只在全部检查结束后才刷新,可选项(网络探测 3×6s、PnP 最长 45s)
没跑完时,即使必需项全绿按钮也是灰的。修复:每完成一项就刷新底部。
- 「管理员权限」原本恒为必需。但 userspace 隧道本身**不需要管理员**,
只有「需要安装 Apple 驱动」时才真的需要。源码模式 / 非管理员启动会被锁死。
修复:`_needs_item()` —— 驱动已就绪时,权限项不再阻塞「进入程序」。
**8. 连接失败时 provider 泄漏 + 启动后重复探测驱动**
`core/server.py` / `main.py`
- `provider_for` 里会话建立失败后,新建的 provider 未关闭(DVT/lockdown 泄漏)。
修复:失败时先关闭再抛出。
- 环境窗口刚做过完整驱动检查,`main()` 启动后又跑一遍深度探测
(sc + 27015 + Get-PnpDevice + 注册表)并可能再次进入安装阶梯。
修复:`setup_result is True` 时跳过重复探测。
**9. keep-alive 连接上「不读 body 的接口」会污染下一个请求**
`core/server.py`
`/api/devices/connect`、`/api/devices/disconnect`、`/api/tunnel`
这些 handler 从不读请求体。在 HTTP/1.1 keep-alive 连接上,没被读走的 `{}`
留在 socket 缓冲区,被 `BaseHTTPRequestHandler` 当成下一个请求的请求行:
```
501 Unsupported method ('{}POST')
```
**间歇性**出现(取决于客户端是否复用连接),最终用户看到的是"偶发操作失败"。
这是我在打包版冒烟测试里实际撞到的。
修复:`do_POST` 进入路由前先把请求体读完并缓存;`handle_one_request` 每请求重置缓存
(否则第二个请求会读到上一个请求的 body —— 这是修复第一版引入的新隐患,已一并处理)。
新增回归脚本 `tools/keepalive_probe.py`(同连接连发 3 个 POST 验证不串)。
### 轻微
**9. 窗口销毁后 Tk 报 `invalid command name "..._pump"`**
`core/setup_window.py`
`_pump` 的 `after` 回调在窗口销毁后仍在队列里。命令行模式下会刷错误。
修复:记录 after id,销毁前取消;并在 `_pump` 开头检查 `winfo_exists()`。
**10. 日志文件无限增长**
`core/server.py`
`app.log` 只追加不滚动,长期使用会越来越大。
修复:启动时若超过 5MB 则滚动为 `app.log.1`(保留一份旧日志)。
**11. 死代码清理**
- `setup_window._update_footer` 里的 `self.state.get("__x__")`(无意义表达式)
- `apple_driver.ensure` 里自相矛盾的 `if result.get("ok"): if name=="msi" and not result.get("ok")`
- `apple_driver.ensure` 里 `service_state()` 的空调用
- `main.py` 的 `driver_state` 字典(写入但从未读取)
- `server._stage_personalized_ddi` 尾部整段不可达代码(残留的 `_cmd_mount_status` 函数体)
---
## 二、功能改进
| 改进 | 说明 |
| --- | --- |
| 环境窗口显示版本号 | 标题旁显示 `v1.1.1 · Windows`,便于确认版本 |
| 环境窗口「打开日志目录」 | 一键打开 `%LOCALAPPDATA%\iOSLocationTrack\logs`,装驱动失败时可自行取证 |
| 网络检查更准确 | 分成「Apple TSS 签名」(必须)和「镜像下载源」(可选)两条,不再把 GitHub 被墙误报成"连不上 Apple" |
| 「重启提权」按钮 | 权限项右侧按钮,点击后通过 UAC 拉起管理员实例,本实例自动退出 |
| `--check-driver` 更完整 | 增加开发者镜像状态与目录、日志目录信息 |
| 驱动安装失败提示 | 明确提示「需要重启 Windows 后生效」而不是笼统的失败 |
---
## 三、验收
```
tests/selftest.py 83/83 PASS
tests/drift_sim.py 40/40 PASS
tests/offline_tiles.py 13/13 PASS
tests/setup_window_selftest.py 7/7 PASS(新增:环境窗口检测→确认→放行全流程)
tests/win_e2e.py 7/7 PASS(真机 iPhone 11 / iOS 26.6.1)
tests/win_app_e2e.py 9/9 PASS(真机,走 App 真实装配路径)
```
打包版 exe 实测:发现设备 → 0.3s 隧道 → DVT → 两次下发 → 恢复真实定位 → 干净退出。
---
## 四、已知未修(留待后续)
审查还提了几项,本轮未动(风险或成本较高,且不影响当前功能):
| 项 | 说明 |
| --- | --- |
| 保活取消的竞态 | `_stop_keepalive()` 只 `cancel()` 不 await,理论上可能与 `clear()/close()` 抢同一个 DTX 通道;窗口极小 |
| `probe()` 失败路径泄漏 lockdown | iOS<17 老路径才有,且失败即报错,影响有限 |
| `provider_for` 长时间持锁 | 连接期间最长持锁约 2 分钟,并发 disconnect/shutdown 会阻塞(无死锁) |
| DDI 下载无 SHA 校验 | 镜像源可变,目前只按文件大小判断完整性 |
| `console=False` 隐藏 CLI 输出 | 打包版 `--check-driver` 的输出看不到,需要另开控制台版本 |
| Win32 调用缺 argtypes | ctypes 未声明原型,当前 64 位下实测正常,但属未定义行为 |
其中建议**下一轮优先**处理:隧道断线自动重连(目前仍需手动断开重连)、
打包版 CLI 输出可见性。