Skip to content

feat: Add iOS 17+ location simulation support via pymobiledevice3 - #219

Closed
bhargavchintam wants to merge 2 commits into
Schlaubischlump:masterfrom
bhargavchintam:feature/ios17-pymobiledevice3-support
Closed

feat: Add iOS 17+ location simulation support via pymobiledevice3#219
bhargavchintam wants to merge 2 commits into
Schlaubischlump:masterfrom
bhargavchintam:feature/ios17-pymobiledevice3-support

Conversation

@bhargavchintam

Copy link
Copy Markdown

Summary

This PR adds iOS 17+ location simulation support using pymobiledevice3, since Apple removed the legacy com.apple.dt.simulatelocation lockdown service starting with iOS 17.

Changes

New Files

  • PMD3Helper.swift — Wraps the pymobiledevice3 CLI for iOS 17+ devices:

    • Auto-discovers the pymobiledevice3 binary
    • Manages tunneld lifecycle (starts with admin privileges via osascript)
    • Uses DVT instruments protocol (developer dvt simulate-location) for persistent location simulation
    • Handles negative coordinates with -- separator
  • PMD3DeviceWrapper.swiftDevice protocol wrapper that routes simulateLocation/disableSimulation through pymobiledevice3 for iOS 17+, while delegating everything else to the original IOSDevice

Modified Files

  • Device+Extension.swift — Starts tunneld during the pairing step for iOS 17+ devices
  • SidebarViewController.swift — Wraps iOS 17+ devices in PMD3DeviceWrapper when selected
  • MapViewController.swift — Shows iOS 17+ specific error messages with installation instructions
  • AppDelegate.swift — Removed auto-opening Info view on version update
  • Main.storyboard — Removed Info preference tab
  • project.pbxproj — Added new source files; disabled ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS to fix Xcode 26 build conflicts with existing NSColor extensions

Documentation

  • README.md — Added iOS 17+ setup section with prerequisites, instructions, and troubleshooting table

How It Works

  1. When an iOS 17+ device is selected, it's wrapped in PMD3DeviceWrapper
  2. During pairing, tunneld is started as a background daemon (requires one-time admin password)
  3. Location simulation uses pymobiledevice3 developer dvt simulate-location set --tunnel <UDID> -- <lat> <lon>
  4. The DVT process runs persistently to maintain the simulated location
  5. On reset/quit, the process is terminated and real GPS resumes

Testing

  • Tested with iPhone 15 Pro running iOS 26.3
  • Verified tunneld starts and tunnel is accessible at http://127.0.0.1:49151
  • Confirmed location changes on device using Apple Maps

Prerequisites for Users

  • Python 3.8+
  • pymobiledevice3 (pip install pymobiledevice3)
  • Developer Mode enabled on the iOS device

- Add PMD3Helper.swift: Wraps pymobiledevice3 CLI for iOS 17+ devices
- Add PMD3DeviceWrapper.swift: Device protocol wrapper for iOS 17+
- Modify Device+Extension.swift: Start tunneld during pairing for iOS 17+
- Modify SidebarViewController.swift: Wrap iOS 17+ devices in PMD3DeviceWrapper
- Modify MapViewController.swift: Show iOS 17+ specific error messages
- Remove Info preference tab (storyboard + AppDelegate auto-open)
- Disable Xcode 26 auto-generated asset symbols (conflict with extensions)
- Update README with iOS 17+ setup instructions and troubleshooting
Copilot AI review requested due to automatic review settings February 25, 2026 07:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds iOS 17+ location simulation support by integrating pymobiledevice3 (RemoteXPC/tunneld + DVT simulate-location) while updating UI, pairing flow, and documentation to reflect the new backend.

Changes:

  • Introduces PMD3Helper and PMD3DeviceWrapper to route iOS 17+ location simulation through pymobiledevice3.
  • Updates device selection/pairing and error messaging to support the new iOS 17+ flow and show setup guidance.
  • Removes the Info preferences tab/auto-open behavior and updates docs/build configuration accordingly.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
README.md Documents iOS 17+ prerequisites, setup, and troubleshooting.
LocationSimulator/ViewController/SidebarViewController/SidebarViewController.swift Wraps selected iOS 17+ devices with PMD3DeviceWrapper.
LocationSimulator/ViewController/MapViewController/MapViewController.swift Shows iOS 17+ specific setup messaging on pairing failures.
LocationSimulator/Storyboard/Main.storyboard Removes Info preference tab UI.
LocationSimulator/Extensions/LocationSpoofer/PMD3Helper.swift Implements CLI discovery, tunneld management, and DVT-based simulation process handling.
LocationSimulator/Extensions/LocationSpoofer/PMD3DeviceWrapper.swift Routes simulate/disable calls to PMD3Helper for iOS 17+.
LocationSimulator/Extensions/LocationSpoofer/Device+Extension.swift Adds requiresPMD3 and updates pairing logic to start tunneld for iOS 17+.
LocationSimulator/AppDelegate.swift Removes version-update auto-open of Info preferences, leaving onboarding behavior.
LocationSimulator.xcodeproj/xcshareddata/xcschemes/LocationSimulator.xcscheme Updates scheme metadata (upgrade version).
LocationSimulator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved Adds SwiftPM resolution file.
LocationSimulator.xcodeproj/project.pbxproj Adds new sources and modifies several build settings related to Xcode upgrade/signing/platforms.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

);
LIBRARY_SEARCH_PATHS = "";
MACH_O_TYPE = mh_execute;
MACOSX_DEPLOYMENT_TARGET = 11.0;

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MACOSX_DEPLOYMENT_TARGET is raised to 11.0 here, but the README still states macOS 10.15+ support. If dropping 10.15 is intentional, the README (and any release notes) should be updated; otherwise, revert this to keep the documented minimum supported macOS version.

Suggested change
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 10.15;

Copilot uses AI. Check for mistakes.
Comment on lines +178 to +183
// Wrap iOS 17+ devices in PMD3DeviceWrapper for pymobiledevice3 support
if let iosDevice = device as? IOSDevice,
PMD3Helper.deviceRequiresPMD3(iosDevice.majorVersion) {
mapViewController.device = PMD3DeviceWrapper(wrapping: iosDevice)
} else {
mapViewController.device = device

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wrapper is applied to all iOS 17+ IOSDevices even when pymobiledevice3 isn’t available. In that case PMD3DeviceWrapper.usesPMD3 is false and pair() short-circuits (because self is no longer an IOSDevice), so the user may see a “connected” state but location simulation won’t work and they won’t get the intended install guidance. Consider only wrapping when PMD3Helper.shared.isAvailable is true, or ensure the wrapper forwards pair()/setup errors so missing pymobiledevice3 is surfaced during pairing.

Copilot uses AI. Check for mistakes.
Comment on lines 59 to +64
// Only real iOS Devices require a pairing if the DeveloperDiskImage is not already mounted
guard let device = self as? IOSDevice, !device.developerDiskImageIsMounted else { return }

// For iOS 17+ without pymobiledevice3, warn the user
if PMD3Helper.deviceRequiresPMD3(device.majorVersion) && !PMD3Helper.shared.isAvailable {
logError("Device+Extension: iOS 17+ detected but pymobiledevice3 is not installed!")

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a device is wrapped in PMD3DeviceWrapper but usesPMD3 is false (e.g., pymobiledevice3 not installed), this method falls through to the guard let device = self as? IOSDevice ... else { return } and silently returns. That bypasses the iOS 17+ “install pymobiledevice3” error path and incorrectly reports pairing success. Consider handling the wrapper case explicitly when usesPMD3 == false (delegate to wrappedDevice.pair() and/or throw a clear error for iOS 17+).

Copilot uses AI. Check for mistakes.
Comment on lines +1717 to +1722
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "LocationSimulator-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUPPORTED_PLATFORMS is set to include iphoneos iphonesimulator and TARGETED_DEVICE_FAMILY is set, but this target is a macOS app. Including iOS platforms here can lead to invalid build settings, confusing scheme destinations, and code signing/entitlement issues. Suggest reverting SUPPORTED_PLATFORMS to macOS-only and removing iOS-only settings like TARGETED_DEVICE_FAMILY.

Suggested change
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "LocationSimulator-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "LocationSimulator-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;

Copilot uses AI. Check for mistakes.
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions disabling asset symbol generation to avoid Xcode build conflicts, but this configuration enables ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES. This can reintroduce the same conflicts depending on which build configuration is used. Consider aligning this setting with the others (set to NO) or document why Homebrew differs.

Suggested change
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO;

Copilot uses AI. Check for mistakes.
INFOPLIST_KEY_NSPrincipalClass = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
MACOSX_DEPLOYMENT_TARGET = 10.15;
MACOSX_DEPLOYMENT_TARGET = 11.0;

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Help target deployment target is bumped to macOS 11.0 here. If the app is still intended to support macOS 10.15 (per README), this change will prevent the bundled help from working on older supported systems. Consider keeping the Help bundle’s deployment target aligned with the app’s minimum supported macOS version.

Suggested change
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 10.15;

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +138
// Use osascript to run tunneld with sudo — this prompts the user for their password
let script = """
do shell script "\(pmd3Path) remote tunneld -d" with administrator privileges
"""

let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["-e", script]

let stdoutPipe = Pipe()
let stderrPipe = Pipe()
task.standardOutput = stdoutPipe
task.standardError = stderrPipe

do {
try task.run()
task.waitUntilExit()

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

osascript “do shell script” runs synchronously until the invoked command exits. If pymobiledevice3 remote tunneld -d is a long-running foreground daemon (as implied by the README description of a background service), waitUntilExit() will block indefinitely and pairing will never complete. Start tunneld in a way that returns immediately (e.g., background the command, use a launchd job, or spawn sudo with a helper) and then poll for readiness.

Copilot uses AI. Check for mistakes.
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = YJ8XYES89R;
DEVELOPMENT_TEAM = 7C252RKZ47;

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These build settings hardcode a specific DEVELOPMENT_TEAM value. This typically breaks building/signing for other contributors/CI and shouldn’t be committed to a shared project. Prefer leaving DEVELOPMENT_TEAM unset (or $(DEVELOPMENT_TEAM)/DEVELOPMENT_TEAM = "") and let developers set it locally.

Suggested change
DEVELOPMENT_TEAM = 7C252RKZ47;
DEVELOPMENT_TEAM = "";

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +124
// Use osascript to run tunneld with sudo — this prompts the user for their password
let script = """
do shell script "\(pmd3Path) remote tunneld -d" with administrator privileges
"""

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ensureTunneld method builds an AppleScript do shell script command by interpolating pmd3Path directly into the shell string that is executed with administrator privileges. If an attacker can influence the resolved pymobiledevice3 path (for example via a crafted PATH entry that contains quotes or shell metacharacters and is picked up by findPMD3Path()), they can inject arbitrary commands into this root shell. To mitigate this, avoid composing shell command strings from untrusted paths (e.g. either properly escape/quote pmd3Path for AppleScript or bypass the shell entirely by invoking pymobiledevice3 via Process with argument arrays).

Copilot uses AI. Check for mistakes.

@bslatyer bslatyer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

@bslatyer bslatyer closed this May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants