docs: 新增 Windows 移植交接文档

docs/WINDOWS_PORT.md 覆盖:
- 逐文件可移植性审计(核心逻辑层约 90% 可直接复用)
- 核心难点:iOS 17+ 的 RSD 隧道无法在 Windows 复用(macOS 实现依赖 XPC +
  ObjC runtime 私有框架),改用 pymobiledevice3 的 userspace 隧道
- 已实测确认的命令与 API(--userspace 挂在 lockdown start-tunnel / mounter
  auto-mount 上,remote start-tunnel 不支持;类名是 UserspaceRsdTunnel)
- 窗口启动、数据目录、打包与分发(DMG → Inno Setup)、图标(.icns → .ico)
- macOS 侧踩过的坑:镜像挂载用 Python API、必须打包包元数据、不要把 distutils
  放进 excludes、AlreadyMountedError 当成功处理
- 设计约束(DVT 长连接、只能改经纬度、last_point 与 last_reported 分离等)
- 验收基线(复用现有测试套件)与建议的工作顺序
This commit is contained in:
jbjf6666 committed 2026-10-16 01:05:09 +08:00
commit 85e9a53750
65 files changed
+18603

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
#!/bin/bash
# 离线抓取 DeveloperDiskImage 素材(绕过 raw.githubusercontent.com 的 SNI 阻断)
#
# 背景(2026-10-14 实测,本机 jbjf-Mac-mini):
# raw.githubusercontent.com 解析到 4 个 GitHub Pages IP,其中 3 个在 TLS ClientHello 后被 RST / 黑洞:
# 185.199.108.133 -> 连接超时(curl exit 28)
# 185.199.109.133 -> RST(curl exit 35)
# 185.199.110.133 -> RST(curl exit 35)
# 185.199.111.133 -> HTTP 200 正常
# DNS 轮询会随机选中坏 IP,而 developer_disk_image/repo.py 的 requests.get 没有重试也没有超时,
# 所以单次 RST 就会让 mounter auto-mount 整体失败(ConnectionResetError(54))。
#
# 本脚本:固定走可用 IP + 重试 + 校验字节数,把素材放到 pymobiledevice3 期望的位置。
# 用法:tools/ddi_offline_fetch.sh [输出目录] 默认输出 /tmp/ddi
set -uo pipefail
OUT="${1:-/tmp/ddi}"
GOOD_IPS=("185.199.111.133" "185.199.108.133" "185.199.109.133" "185.199.110.133")
BASE="https://raw.githubusercontent.com/doronz88/DeveloperDiskImage/main"
CACHE="$HOME/.pymobiledevice3/Xcode_iOS_DDI_Personalized"
PY="$(cd "$(dirname "$0")/.." && pwd)/.venv/bin/python"
mkdir -p "$OUT/16.5" "$CACHE"
# fetch <仓库相对路径> <期望字节数> <输出文件>
fetch() {
local path="$1" want="$2" dest="$3"
for ip in "${GOOD_IPS[@]}"; do
echo "--- $path (期望 $want 字节, 尝试 $ip)"
if curl -sS -m 300 --retry 3 --retry-all-errors --resolve "raw.githubusercontent.com:443:$ip" \
-o "$dest.part" -w " http=%{http_code} size=%{size_download} t=%{time_total}s\n" "$BASE/$path"; then
local got; got=$(stat -f%z "$dest.part" 2>/dev/null || echo 0)
if [ "$got" = "$want" ]; then mv "$dest.part" "$dest"; echo " OK ($ip)"; return 0; fi
echo " 字节数不符 ($got != $want),换 IP"
fi
done
echo "!!! $path 全部 IP 失败"; return 1
}
# iOS 17+(personalized)素材 —— 例如 iPhone 11 / iOS 26.6.1
fetch "PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg" 15733248 "$OUT/Image.dmg"
fetch "PersonalizedImages/Xcode_iOS_DDI_Personalized/BuildManifest.plist" 801505 "$OUT/BuildManifest.plist"
fetch "PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg.trustcache" 1895 "$OUT/Image.dmg.trustcache"
# iOS <17(经典 developer image)素材 —— 例如 iOS 16.5
fetch "DeveloperDiskImages/16.5/DeveloperDiskImage.dmg" 7427565 "$OUT/16.5/DeveloperDiskImage.dmg"
fetch "DeveloperDiskImages/16.5/DeveloperDiskImage.dmg.signature" 128 "$OUT/16.5/DeveloperDiskImage.dmg.signature"
# 写进 pymobiledevice3 缓存:fetch_personalized_ddi() 会直接命中,彻底不再联网
# 注意缓存里的 trustcache 文件名必须是 Image.trustcache(不是 Image.dmg.trustcache)
cp "$OUT/BuildManifest.plist" "$CACHE/BuildManifest.plist"
cp "$OUT/Image.dmg" "$CACHE/Image.dmg"
cp "$OUT/Image.dmg.trustcache" "$CACHE/Image.trustcache"
echo
echo "=== 校验缓存(ProductBuildVersion 必须等于 pymobiledevice3 的 LATEST_DDI_BUILD_ID)==="
"$PY" - <<'PY'
import plistlib, pathlib
from pymobiledevice3.services.mobile_image_mounter import LATEST_DDI_BUILD_ID, fetch_personalized_ddi
c = pathlib.Path.home()/".pymobiledevice3"/"Xcode_iOS_DDI_Personalized"
bm = plistlib.loads((c/"BuildManifest.plist").read_bytes())
print("缓存 BuildVersion :", bm["ProductBuildVersion"])
print("期望 LATEST_DDI :", LATEST_DDI_BUILD_ID)
print("缓存有效(不会重下):", bm["ProductBuildVersion"] == LATEST_DDI_BUILD_ID)
PY
echo
echo "=== 接下来可以直接离线挂载(不再触碰 GitHub)==="
echo " # iOS 17+ 设备(personalized,走缓存)"
echo " ./.venv/bin/python -m pymobiledevice3 mounter auto-mount --udid <UDID>"
echo " # iOS <17 设备(经典镜像,显式指定本地文件)"
echo " ./.venv/bin/python -m pymobiledevice3 mounter mount-developer \\"
echo " $OUT/16.5/DeveloperDiskImage.dmg $OUT/16.5/DeveloperDiskImage.dmg.signature --udid <UDID>"