按 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 → 两次下发 → 恢复真实定位 → 干净退出
295 lines
17 KiB
Markdown
295 lines
17 KiB
Markdown
# iOS Virtual Location & Route Simulator (macOS / Windows)
|
||
|
||
[中文](README.md) | [**English**](README.en.md)
|
||
|
||
A desktop tool for **virtual location** and **route simulation** on iPhone / iPad: draw a route on the map, add waypoints, and drive the device's location along it at realistic speeds.
|
||
|
||
Supports **iOS 17 – iOS 27.2** (RSD tunnel + DVT `SimulateLocation`). The developer disk image assets are bundled inside the app, so **no Python installation is required**.
|
||
|
||
The UI is a local web app opened in a Chrome / Edge app window (no address bar — looks like a native app). You can also just open `http://127.0.0.1:8787` in a browser. The map, route engine, playback engine, and device communication all run locally; only map tiles and optional route planning need network access.
|
||
|
||
> **Windows users: see [docs/README-WINDOWS.md](docs/README-WINDOWS.md).**
|
||
> The Windows build establishes the tunnel **without administrator rights** (pure-Python
|
||
> userspace stack — no Wintun driver needed), detects and auto-installs Apple Mobile Device
|
||
> Support on first launch, and shows an environment self-check window.
|
||
> Build outputs: `dist\iOSLocationTrack\iOSLocationTrack.exe` and
|
||
> `dist\installer\iOSLocationTrack-1.0.0-setup.exe`.
|
||
|
||
---
|
||
|
||
## 1. Quick Start
|
||
|
||
### Option A — Download the installer (recommended)
|
||
|
||
Grab `iOSLocationTrack-1.0.0.dmg` from [Releases](https://github.com/jbjf6666/ios-location-track/releases):
|
||
|
||
1. Open the DMG and drag `虚拟定位轨迹.app` into `Applications`
|
||
2. If macOS says *"unidentified developer"* on first launch → **right-click → Open → Open**
|
||
|
||
**No Python, no dependencies** — the Python runtime and every dependency are bundled inside the app (~80 MB).
|
||
Config and tile cache are written to `~/Library/Application Support/iOSLocationTrack/`.
|
||
|
||
> **Apple Silicon (M-series) only.** For Intel Macs, use the source option below, or run `./scripts/build_app.sh` on an Intel machine (the script is architecture-agnostic).
|
||
|
||
### Option B — Run from source
|
||
|
||
```bash
|
||
git clone https://github.com/jbjf6666/ios-location-track.git
|
||
cd ios-location-track
|
||
|
||
# First run creates a virtualenv and installs dependencies automatically
|
||
./scripts/run.sh
|
||
```
|
||
|
||
The UI opens automatically. Equivalent CLI usage:
|
||
|
||
```bash
|
||
./.venv/bin/python main.py # normal launch (opens the UI)
|
||
./.venv/bin/python main.py --mock # no-device mock mode (demo the route/playback)
|
||
./.venv/bin/python main.py --port 8899 --no-browser
|
||
./.venv/bin/python main.py --window browser # open in the default browser instead
|
||
```
|
||
|
||
### Three ways to run it
|
||
|
||
| Form | Path | Environment needed | Use case |
|
||
| --- | --- | --- | --- |
|
||
| **DMG installer** | Release asset (45 MB) | **None** | Distribution / moving to another Mac |
|
||
| **Standalone app** | `dist/虚拟定位轨迹.app` (~80 MB extracted) | **None** — Python, deps, web UI, icon, disk image assets are all inside | Double-click and go |
|
||
| Source | `./scripts/run.sh` | Python 3.11+ (deps installed on first run) | Hacking on the code |
|
||
|
||
```bash
|
||
# Rebuild everything in one shot: icon → bundle → ad-hoc sign → DMG → verify
|
||
./scripts/build_app.sh
|
||
```
|
||
|
||
The DMG contains the app, an `Applications` shortcut (drag to install), and an install note.
|
||
To customize the DMG appearance, edit the constants at the top of `tools/make_dmg.py`.
|
||
|
||
> On first launch macOS will ask for permission to control Chrome/Edge (used to open the app window).
|
||
|
||
---
|
||
|
||
## 2. Supported iOS Versions
|
||
|
||
| Version | Supported | Notes |
|
||
| --- | --- | --- |
|
||
| **iOS 17 – 27.2** | ✅ | RSD tunnel + DVT. The app bundles the shared personalized disk image assets |
|
||
| iOS 16 and earlier | ❌ | Their developer disk images must be signed by Apple per OS build; third-party images are rejected by the device (verified: `Failed to verify the disk image signature`, and libimobiledevice fails identically) |
|
||
|
||
**Why one image covers everything:** starting with iOS 17, Apple switched to *personalized* developer disk images — a single base image (`Image.dmg`) plus `BuildManifest.plist`, **shared across all OS versions**. The actual signature is issued online by Apple's TSS server per device at mount time, so per-version assets are unnecessary. This tool bundles those assets (~16 MB); clicking **"Mount Developer Image"** works offline (only the signing step needs network).
|
||
|
||
---
|
||
|
||
## 3. Device Preparation (important)
|
||
|
||
| Requirement | Details |
|
||
| --- | --- |
|
||
| Cable connection | USB recommended; Wi-Fi drops easily when transferring large files |
|
||
| Unlock and **Trust** | Tap **Trust** on the *"Trust This Computer?"* prompt and enter your passcode |
|
||
| Enable Developer Mode | Settings → Privacy & Security → **Developer Mode** → on → **restart the phone** → confirm after reboot |
|
||
| Mount developer image | Click **"Mount Developer Image"** in the UI (assets are bundled), or let Xcode do it once |
|
||
| Close competing tools | i4Tools, 爱思助手, Xcode, etc. fight over the same RSD connection — **only one at a time** |
|
||
|
||
### Mounting the developer image
|
||
|
||
Click **"Mount Developer Image"** in the UI; progress streams into the log panel on the right. CLI equivalent:
|
||
|
||
```bash
|
||
# Check whether it's already mounted ( [] means not mounted )
|
||
./.venv/bin/python -m pymobiledevice3 mounter list
|
||
|
||
# Mount automatically
|
||
./.venv/bin/python -m pymobiledevice3 mounter auto-mount --udid <YOUR_UDID>
|
||
```
|
||
|
||
Or just use Xcode: connect the device → Window → Devices and Simulators → wait for *"Preparing device for development"*.
|
||
|
||
> With Xcode installed, the system's `CoreDeviceService` usually mounts a local image
|
||
> (`/Library/Developer/DeveloperDiskImages/`) automatically when the device is plugged in.
|
||
|
||
> The packaged `.app` performs mounting through pymobiledevice3's **Python API** — no command-line tools required.
|
||
|
||
### About the iOS 17+ tunnel (RSD)
|
||
|
||
Since iOS 17, location simulation no longer goes over usbmux; an **RSD tunnel** must be established first. Three options:
|
||
|
||
1. **Rootless (default, recommended)**: runs `pymobiledevice3 remote start-tunnel --native --script-mode`, borrowing Apple's own `remoted` tunnel. The packaged app does this by **re-invoking itself** (`<app> tunnel-helper`), so **no external Python is needed**.
|
||
2. **Admin mode**: click *"Rebuild tunnel as administrator"* in the UI (macOS password prompt), or run `sudo ./scripts/run.sh`.
|
||
3. **Manual RSD**: the *"Manual RSD"* button — enter `HOST PORT` obtained elsewhere (e.g. `tunneld`).
|
||
|
||
> `remote tunneld`, `lockdown start-tunnel`, and the classic `remote start-tunnel` all **require root**; this tool never silently escalates privileges.
|
||
|
||
---
|
||
|
||
## 4. UI Overview
|
||
|
||
### Top bar: device
|
||
- Name / model / iOS version / connection type (USB or network)
|
||
- **Tunnel** status, **virtual location** status, **Developer Mode** status
|
||
- *Refresh*, *Connect*, *Disconnect*
|
||
|
||
### Left panel: route settings
|
||
- **Travel mode**: driving / cycling / walking / flying (sets base speed and whether real roads are used)
|
||
- **Playback speed**: 0.1x – 50x (affects route progression only, not the send rate)
|
||
- **Loop**: return to start at the end
|
||
- **Realism simulation** (toggleable anytime, even mid-playback):
|
||
- **Location drift**: slow wandering that mimics GPS error, radius 0–50 m (default 4 m).
|
||
Implemented with multi-octave smoothed noise — continuous along the route with inertia, so it **never produces random jumps**.
|
||
- **Speed jitter**: instantaneous speed fluctuates smoothly around the setpoint, 0–60% (default ±8%).
|
||
- **Send-timing jitter** (on by default): a real phone's GPS does not fire exactly once per second — it jitters 0.85–1.15 s with occasional dropped samples (~2%). Perfectly uniform intervals are one of the easiest things to spot in the data.
|
||
- **Derive speed from displacement** (on by default): reported speed = actual displacement ÷ actual interval, matching how iOS derives `CLLocation.speed` from the coordinate stream, so **speed, displacement and timestamps stay mutually consistent**.
|
||
- **Inject gross errors** (off by default): probabilistic bad fixes (multipath / urban canyon), for testing your own app's `horizontalAccuracy` filtering and outlier rejection.
|
||
- All toggles and magnitudes persist across restarts.
|
||
- **Jump to coordinate**: type a lat/lng directly; save favourite places
|
||
- **Route summary**: total distance, estimated duration, path point count, waypoint count, route engine (OSRM real roads / straight line)
|
||
- **Waypoint list**: drag to reorder, double-click to edit coordinates, delete, centre on point
|
||
- **Saved routes**: save / load / delete (browser local storage)
|
||
- **Base map**: AMap vector (GCJ-02, Chinese labels) / AMap satellite / global satellite (Esri) / OpenStreetMap
|
||
- Actions: *Load OSRM route*, *Clear*, *Export GPX*, *Import GPX*, *Restore real location*
|
||
- Tile cache: shows directory and size, plus *Clear tile cache*
|
||
|
||
### Map toolbar
|
||
- **Select**: pan the map, drag waypoints, click to edit
|
||
- **Add waypoint**: click repeatedly on the map to add points (the most common way to draw)
|
||
- **Draw polyline**: click to draw, double-click or Esc to finish
|
||
- **Undo**, **Delete selected point**, **Fit to route**
|
||
|
||
### Bottom bar: playback
|
||
- Play / Pause / Resume / Stop
|
||
- Scrubbable progress bar (seek)
|
||
- Elapsed time, remaining time, sample index
|
||
- Live speed, current coordinates, playback rate
|
||
|
||
### Right panel: log
|
||
Live log of device connection, tunnel setup, image mounting, and every location push — makes troubleshooting much easier.
|
||
|
||
---
|
||
|
||
## 5. Typical Workflows
|
||
|
||
**A. Run a route along real roads**
|
||
|
||
1. Connect the device → *Connect* (iOS 17+ builds the tunnel automatically, up to ~1 minute)
|
||
2. Click *Add waypoint* and place start / intermediate / end points (at least 2)
|
||
3. Click *Load OSRM route* → real road-network polyline (falls back to a straight line offline)
|
||
4. Set *Playback speed* (e.g. 20x completes 10 km in a few seconds)
|
||
5. Click *Play* → the phone's location moves along the route; map marker and progress update live
|
||
6. Click *Stop* → the phone immediately returns to its real location
|
||
|
||
**B. Pin to a single location**
|
||
|
||
With the *Select* tool, double-click anywhere on the map, or use *Jump to coordinate*. Click *Restore real location* when done.
|
||
|
||
**C. Import an existing track**
|
||
|
||
Click *Import GPX* and pick a `.gpx` file → the track appears on the map → play it. Exported GPX files also import into Google Earth, Strava, etc.
|
||
|
||
**D. Preview without a device**
|
||
|
||
```bash
|
||
./.venv/bin/python main.py --mock
|
||
```
|
||
The UI, route drawing, playback engine and progress bar all work; locations just aren't pushed to a phone (the top bar shows *"mock mode"*).
|
||
|
||
---
|
||
|
||
## 6. CLI Tools and Self-Tests
|
||
|
||
```bash
|
||
# List devices + Developer Mode + DVT availability
|
||
./.venv/bin/python tools/probe_device.py
|
||
./.venv/bin/python tools/probe_device.py --probe # actually open a DVT session
|
||
|
||
# One-shot virtual location from the CLI
|
||
./.venv/bin/python tools/probe_location.py --lat 39.9088 --lon 116.3975
|
||
./.venv/bin/python tools/probe_location.py --clear # restore real location
|
||
|
||
# Offline self-test: 83 unit + end-to-end checks (no device needed)
|
||
./.venv/bin/python tests/selftest.py
|
||
|
||
# Realism model (GPS drift + speed jitter + timing jitter, 40 checks)
|
||
./.venv/bin/python tests/drift_sim.py
|
||
|
||
# Tile disk cache (13 checks) / browser-level offline tiles (9 checks)
|
||
./.venv/bin/python tests/offline_tiles.py
|
||
./.venv/bin/python tests/offline_browser.py
|
||
|
||
# UI acceptance (20 checks, drives a real browser)
|
||
./.venv/bin/python tests/ui_acceptance.py
|
||
|
||
# Verify the packaged standalone app: clean-env launch + all endpoints (22 checks)
|
||
./.venv/bin/python tools/verify_bundle.py
|
||
|
||
# Real-device end-to-end: actually moves the phone's location, restores afterwards
|
||
./.venv/bin/python tests/real_device_e2e.py --confirm --duration 5 --speed 20
|
||
|
||
# Screenshot the UI (visual acceptance)
|
||
./.venv/bin/python tools/screenshot.py http://127.0.0.1:8787/ /tmp/ui.png --wait 9
|
||
```
|
||
|
||
Packaging helpers:
|
||
|
||
```bash
|
||
./.venv/bin/python tools/make_icon.py # generate the app icon (drawn programmatically)
|
||
./.venv/bin/python tools/fetch_ddi.py # fetch developer disk image assets
|
||
./.venv/bin/python tools/make_dmg.py # build the DMG
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Troubleshooting
|
||
|
||
| Symptom | Cause and fix |
|
||
| --- | --- |
|
||
| UI says "local service not connected" | Server didn't start or the port is taken. Check the terminal; try `--port 8899` |
|
||
| No device detected | Cable + unlocked + tap **Trust** |
|
||
| Prompted for Developer Mode | Settings → Privacy & Security → Developer Mode → on, then restart the phone |
|
||
| "Mount Developer Image" fails with a signature error | The device runs iOS 16 or earlier (unsupported); or the network is blocking Apple TSS. Check connectivity and retry |
|
||
| Tunnel-related errors when connecting | ① Close i4Tools / 爱思助手 (they steal the RSD connection); ② use *Rebuild tunnel as administrator*; ③ confirm the developer image is mounted |
|
||
| Location doesn't change on the phone | Make sure the UI shows "simulating"; on iOS 17+ the tunnel must be healthy |
|
||
| Blank map | Tile source blocked — switch base map (AMap satellite / Esri). **Previously viewed areas work offline** (tiles cached in `~/.ios-location-tiles`, 1 GB cap, LRU eviction) |
|
||
| Location snaps back right after the tunnel is up | Another program (i4Tools / Xcode) is fighting for the same RSD connection — close it |
|
||
|
||
---
|
||
|
||
## 8. Implementation Notes
|
||
|
||
- **Transport**: Python stdlib `http.server` plus a hand-written WebSocket (RFC 6455) — no web framework. Device side uses `pymobiledevice3`.
|
||
- **Virtual location**: DVT service `com.apple.instruments.dtservicehub` (iOS 17+, over the RSD tunnel), calling `LocationSimulation.set(lat, lon)` / `clear()`.
|
||
**The connection must stay open** — once the DVT session drops, simulated location stops working. That's why the playback engine can't reconnect per point.
|
||
- **Playback engine**: advances by arc-length integration; the speed profile is generated from turn angles (slower in sharp corners) and then smoothed, so acceleration feels natural. A dedicated thread with a single in-flight request guarantees strictly ordered delivery.
|
||
- **Realism model**: drift uses **multi-octave value noise** (120 m slow + 30 m medium + 15 m fast correlation lengths, approximating the power-law spectrum of real GPS error), normalized to the configured radius and clipped at 1.8x; consecutive fixes transition smoothly, so there are no random-jump spikes.
|
||
- **Coordinates**: everything internal and backend-side is **WGS84**; AMap tiles are GCJ-02, converted in the frontend both for tile requests and marker placement — otherwise the marker and the actual location would be ~500 m apart.
|
||
- **Tiles**: server-side proxy plus disk cache (`~/.ios-location-tiles`, 1 GB cap, LRU). Fully transparent "empty tiles" returned by providers are detected and discarded.
|
||
- **Routing**: prefers OSRM real roads (`router.project-osrm.org`); falls back to a straight line offline and labels the engine type on the map.
|
||
- **Standalone packaging**: PyInstaller onedir + ad-hoc signature. Tunnel establishment uses **self-invocation** (`<app binary> tunnel-helper`), so the packaged app needs no Python on the machine.
|
||
- **Protocol**: see [docs/PROTOCOL.md](docs/PROTOCOL.md).
|
||
- **Windows port**: see [docs/WINDOWS_PORT.md](docs/WINDOWS_PORT.md) (portability audit, tunnel
|
||
replacement, packaging changes, acceptance baseline). Roughly 90% of the core logic is reusable;
|
||
only tunnel establishment and the distribution pipeline need rewriting. (Written in Chinese.)
|
||
|
||
---
|
||
|
||
## 9. Known Limitations
|
||
|
||
- **DVT can only set latitude/longitude**: `simulateLocationWithLatitude:longitude:` takes exactly two arguments. `horizontalAccuracy`, `verticalAccuracy`, `altitude`, `floor`, `sourceInformation.isSimulatedBySoftware`, and all accelerometer / gyroscope / pedometer data are either synthesized by iOS or come from hardware — **they cannot be injected over the DVT channel**. This is a shared boundary of every tool in this category, including Xcode's own location simulation.
|
||
- **Some apps are unaffected**: apps using a separate location channel, or performing server-side kinematic cross-validation, may either ignore the simulated location or accept it and discard the data.
|
||
- **Only one tool can hold the tunnel at a time**: an iOS 17+ device exposes a single RSD connection, so i4Tools / Xcode and this tool will kick each other off.
|
||
- The image-signing step requires reachable Apple TSS servers.
|
||
- Map tiles come from public services and are intended for personal testing only — respect their terms of service.
|
||
|
||
---
|
||
|
||
## 10. Acceptable Use
|
||
|
||
This tool is intended for **software testing, development debugging, and regression verification of location-dependent features**, for example:
|
||
|
||
- Testing how your app renders and behaves in different cities or countries
|
||
- Verifying edge cases in route replay, geofencing, and check-in style features
|
||
- Demos and teaching
|
||
|
||
Do not use it to deceive others, falsify travel records, evade attendance systems, or for any unlawful purpose. You are responsible for any consequences of using this tool.
|
||
|
||
In particular: this tool **does not** provide any capability to bypass third-party anti-cheat or risk-control systems. If you need to test features that depend on altitude, barometer, or pedometer data, the only reliable approach is to add a location-injection hook to a Debug build of your own app and construct a complete `CLLocation` yourself.
|