Files
ios-location-track/scripts/build.ps1
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

163 lines
6.6 KiB
PowerShell
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.
# iOS 虚拟定位轨迹工具 —— Windows 构建脚本
#
# 产出:
# dist\iOSLocationTrack\iOSLocationTrack.exe 绿色版(直接双击运行)
# dist\installer\iOSLocationTrack-1.0.0-setup.exe 安装包(需要 Inno Setup 6)
#
# 用法(在项目根目录):
# .\scripts\build.ps1 # 打包 exe + 安装包(若装了 Inno Setup)
# .\scripts\build.ps1 -NoInstaller # 只打包 exe
# .\scripts\build.ps1 -SkipTests # 跳过测试
#
# 前置:
# 1. 已运行过 .\scripts\run.ps1(或手动建好 .venv 并装好依赖)
# 2. 图标:resources\AppIcon.ico(缺失时自动用 tools\make_ico.py 生成)
# 3. 开发者镜像:resources\ddi\personalized\Image.dmg(缺失时自动下载)
# 4. Inno Setup 6(可选):winget install -e --id JRSoftware.InnoSetup
[CmdletBinding()]
param(
[switch]$NoInstaller,
[switch]$SkipTests,
[switch]$SkipIcon,
[switch]$SkipDdi
)
$ErrorActionPreference = 'Stop'
function Write-Bold { param($m) Write-Host "`n=== $m ===" -ForegroundColor White }
function Write-Ok { param($m) Write-Host " [OK] $m" -ForegroundColor Green }
function Write-Warn { param($m) Write-Host " [!!] $m" -ForegroundColor Yellow }
function Write-Fail { param($m) Write-Host " [XX] $m" -ForegroundColor Red }
$Root = Split-Path -Parent $PSScriptRoot
Set-Location $Root
$VenvPy = Join-Path $Root '.venv\Scripts\python.exe'
if (-not (Test-Path $VenvPy)) {
Write-Fail "找不到 $VenvPy。请先运行 .\scripts\run.ps1 创建环境。"
exit 1
}
# ---------- 0. 构建依赖 ----------
Write-Bold "0. 构建依赖"
& $VenvPy -c "import PyInstaller, PIL" 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Warn "缺少 PyInstaller / Pillow,正在安装…"
$idx = if ($env:IOSLOC_PIP_INDEX) { $env:IOSLOC_PIP_INDEX } else { 'https://mirrors.aliyun.com/pypi/simple/' }
& $VenvPy -m pip install --disable-pip-version-check -i $idx pyinstaller pillow
if ($LASTEXITCODE -ne 0) { Write-Fail "构建依赖安装失败"; exit 1 }
}
Write-Ok "PyInstaller / Pillow 就绪"
# ---------- 1. 图标 ----------
if (-not $SkipIcon) {
Write-Bold "1. Windows 图标(.ico)"
if ((Test-Path 'resources\AppIcon.ico') -and
((Get-Item 'resources\AppIcon.ico').LastWriteTime -gt (Get-Item 'resources\AppIcon.iconset').LastWriteTime)) {
Write-Ok "resources\AppIcon.ico 已是最新"
} else {
& $VenvPy tools\make_ico.py
if ($LASTEXITCODE -ne 0) { Write-Fail "图标生成失败"; exit 1 }
}
}
# ---------- 2. 开发者镜像 ----------
if (-not $SkipDdi) {
Write-Bold "2. 开发者镜像素材"
$imageDmg = 'resources\ddi\personalized\Image.dmg'
if (Test-Path $imageDmg) {
$mb = [math]::Round((Get-Item $imageDmg).Length / 1MB, 2)
Write-Ok "$imageDmg 已存在($mb MB)"
} else {
Write-Warn "$imageDmg 缺失(仓库中不入库),正在下载…"
& $VenvPy tools\fetch_ddi.py --personalized-only
if (-not (Test-Path $imageDmg)) {
Write-Fail "镜像下载失败。iOS 17+ 挂载会退化为在线获取,打包产物不完整。"
Write-Fail "可稍后重试:$VenvPy tools\fetch_ddi.py --personalized-only"
exit 1
}
}
}
# ---------- 3. 测试 ----------
if (-not $SkipTests) {
Write-Bold "3. 测试(无需设备)"
$failed = 0
foreach ($t in @('tests\selftest.py', 'tests\drift_sim.py', 'tests\offline_tiles.py')) {
Write-Host " --- $t"
& $VenvPy $t 2>&1 | Select-Object -Last 3
if ($LASTEXITCODE -ne 0) { $failed++; Write-Fail "$t 失败" }
}
if ($failed -gt 0) { Write-Fail "$failed 个测试套件失败,已中止打包"; exit 1 }
Write-Ok "全部测试通过"
}
# ---------- 4. PyInstaller ----------
Write-Bold "4. 打包 exe(onedir + requireAdministrator 清单)"
if (Test-Path 'dist\iOSLocationTrack') { Remove-Item 'dist\iOSLocationTrack' -Recurse -Force }
& $VenvPy -m PyInstaller --clean --noconfirm Location.spec
if ($LASTEXITCODE -ne 0) { Write-Fail "PyInstaller 打包失败"; exit 1 }
$exe = 'dist\iOSLocationTrack\iOSLocationTrack.exe'
if (-not (Test-Path $exe)) { Write-Fail "未生成 $exe"; exit 1 }
$sizeMb = [math]::Round(((Get-ChildItem 'dist\iOSLocationTrack' -Recurse -File |
Measure-Object Length -Sum).Sum / 1MB), 1)
Write-Ok "$exe(整包 $sizeMb MB)"
# ---------- 5. 校验清单里的 requireAdministrator ----------
Write-Bold "5. 校验 exe 是否请求管理员权限"
$manifestOk = $false
try {
# manifest 嵌在 exe 资源里;用 findstr 直接找 UTF-8 明文即可
$bytes = [System.IO.File]::ReadAllBytes((Resolve-Path $exe))
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text -match 'requireAdministrator') {
$manifestOk = $true
Write-Ok "清单包含 requireAdministrator(启动时会弹 UAC)"
}
} catch { }
if (-not $manifestOk) {
Write-Warn "未能在 exe 中确认 requireAdministrator —— 请手动确认 Location.spec 的 uac_admin=True"
}
# ---------- 6. Inno Setup ----------
if ($NoInstaller) {
Write-Bold "6. 跳过安装包(-NoInstaller)"
} else {
Write-Bold "6. 制作安装包(Inno Setup)"
$iscc = $null
foreach ($candidate in @(
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
"$env:ProgramFiles\Inno Setup 6\ISCC.exe",
"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe",
"$env:LOCALAPPDATA\Inno Setup 6\ISCC.exe"
)) {
if ($candidate -and (Test-Path $candidate)) { $iscc = $candidate; break }
}
if (-not $iscc) {
$cmd = Get-Command ISCC.exe -ErrorAction SilentlyContinue
if ($cmd) { $iscc = $cmd.Source }
}
if (-not $iscc) {
Write-Warn "未找到 Inno Setup 6,跳过安装包。"
Write-Warn "安装:winget install -e --id JRSoftware.InnoSetup"
Write-Warn "然后重跑:.\scripts\build.ps1 -SkipTests -SkipIcon -SkipDdi"
} else {
Write-Host " 使用 $iscc"
& $iscc 'installer\iosloc.iss'
if ($LASTEXITCODE -ne 0) { Write-Fail "Inno Setup 编译失败"; exit 1 }
Get-ChildItem 'dist\installer\*.exe' | ForEach-Object {
Write-Ok "$($_.FullName)($([math]::Round($_.Length/1MB,1)) MB)"
}
}
}
Write-Bold "完成"
Write-Host " 绿色版:dist\iOSLocationTrack\iOSLocationTrack.exe"
if (Test-Path 'dist\installer') {
Get-ChildItem 'dist\installer\*.exe' -ErrorAction SilentlyContinue | ForEach-Object {
Write-Host " 安装包:$($_.FullName)"
}
}
Write-Host "`n 提示:exe 启动时会请求管理员权限(用于首次自动安装 Apple Mobile Device Support)。"