From fe5e60b15b54df82662d8ea1ab592a2d3d200bdc Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 2 May 2024 09:54:44 +0200 Subject: [PATCH] feat: Simulate location for iOS17 (#403) The current setlocation only works for iOS below 17, the iOS 17 introduces a new instrumentation service com.apple.instruments.dtservicehub with the channel id com.apple.instruments.server.services.LocationSimulation for the simulated location. Co-authored-by: fish-sauce --- ios/debugproxy/binforward.go | 1 + ios/instruments/helper.go | 5 +++ ios/instruments/location_simulation.go | 50 ++++++++++++++++++++++++++ ios/listdevices.go | 6 ++++ main.go | 39 ++++++++++++++++++-- 5 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 ios/instruments/location_simulation.go diff --git a/ios/debugproxy/binforward.go b/ios/debugproxy/binforward.go index cb986252..92735458 100644 --- a/ios/debugproxy/binforward.go +++ b/ios/debugproxy/binforward.go @@ -22,6 +22,7 @@ var serviceConfigurations = map[string]serviceConfig{ "com.apple.accessibility.axAuditDaemon.remoteserver": {NewDtxDecoder, true}, "com.apple.testmanagerd.lockdown": {NewDtxDecoder, true}, "com.apple.debugserver": {NewBinDumpOnly, true}, + "com.apple.instruments.dtservicehub": {NewDtxDecoder, false}, "com.apple.instruments.remoteserver.DVTSecureSocketProxy": {NewDtxDecoder, false}, "com.apple.testmanagerd.lockdown.secure": {NewDtxDecoder, false}, "bindumper": {NewBinDumpOnly, false}, diff --git a/ios/instruments/helper.go b/ios/instruments/helper.go index 0d5a9932..42d19d1b 100644 --- a/ios/instruments/helper.go +++ b/ios/instruments/helper.go @@ -12,6 +12,7 @@ import ( const ( serviceName string = "com.apple.instruments.remoteserver" serviceNameiOS14 string = "com.apple.instruments.remoteserver.DVTSecureSocketProxy" + serviceNameRsd string = "com.apple.instruments.dtservicehub" ) type loggingDispatcher struct { @@ -24,6 +25,10 @@ func (p loggingDispatcher) Dispatch(m dtx.Message) { } func connectInstruments(device ios.DeviceEntry) (*dtx.Connection, error) { + if device.SupportsRsd() { + log.Debugf("Connecting to %s", serviceNameRsd) + return dtx.NewTunnelConnection(device, serviceNameRsd) + } dtxConn, err := dtx.NewUsbmuxdConnection(device, serviceName) if err != nil { log.Debugf("Failed connecting to %s, trying %s", serviceName, serviceNameiOS14) diff --git a/ios/instruments/location_simulation.go b/ios/instruments/location_simulation.go new file mode 100644 index 00000000..70d2a6d8 --- /dev/null +++ b/ios/instruments/location_simulation.go @@ -0,0 +1,50 @@ +package instruments + +import ( + "github.com/danielpaulus/go-ios/ios" + dtx "github.com/danielpaulus/go-ios/ios/dtx_codec" +) + +const locationSimulationIdentifier = "com.apple.instruments.server.services.LocationSimulation" + +// LocationSimulationService gives us access to simulate device geo location +type LocationSimulationService struct { + channel *dtx.Channel + conn *dtx.Connection +} + +// StartSimulateLocation sets geolocation with provided params +func (d *LocationSimulationService) StartSimulateLocation(lat, lon float64) error { + _, err := d.channel.MethodCall("simulateLocationWithLatitude:longitude:", lat, lon) + if err != nil { + return err + } + + return nil +} + +// StopSimulateLocation clears simulated location +func (d *LocationSimulationService) StopSimulateLocation() error { + _, err := d.channel.MethodCall("stopLocationSimulation") + if err != nil { + return err + } + defer d.Close() + + return nil +} + +// NewLocationSimulationService creates a new LocationSimulationService for a given device +func NewLocationSimulationService(device ios.DeviceEntry) (*LocationSimulationService, error) { + dtxConn, err := connectInstruments(device) + if err != nil { + return nil, err + } + processControlChannel := dtxConn.RequestChannelIdentifier(locationSimulationIdentifier, loggingDispatcher{dtxConn}) + return &LocationSimulationService{channel: processControlChannel, conn: dtxConn}, nil +} + +// Close closes up the DTX connection +func (d *LocationSimulationService) Close() { + d.conn.Close() +} diff --git a/ios/listdevices.go b/ios/listdevices.go index acf6fdb5..9cd49584 100755 --- a/ios/listdevices.go +++ b/ios/listdevices.go @@ -82,6 +82,12 @@ func NewReadDevices() ReadDevicesType { return data } +// SupportsRsd checks if the device supports RSD (Remote Service Discovery). +// It returns true if the device has RSD capability, otherwise false. +func (device *DeviceEntry) SupportsRsd() bool { + return device.Rsd != nil +} + // ListDevices returns a DeviceList containing data about all // currently connected iOS devices func (muxConn *UsbMuxConnection) ListDevices() (DeviceList, error) { diff --git a/main.go b/main.go index 8a3a99ff..31740332 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "path/filepath" "runtime/debug" "sort" + "strconv" "strings" "syscall" "time" @@ -156,7 +157,7 @@ The commands work as following: ios info [display | lockdown] [options] Prints a dump of device information from the given source. ios image list [options] List currently mounted developers images' signatures ios image mount [--path=] [options] Mount a image from - > For iOS 17+ (personalized developer disk images) must point to the "Restore" directory inside the developer disk + > For iOS 17+ (personalized developer disk images) must point to the "Restore" directory inside the developer disk ios image auto [--basedir=] [options] Automatically download correct dev image from the internets and mount it. > You can specify a dir where images should be cached. > The default is the current dir. @@ -216,7 +217,7 @@ The commands work as following: ios apps [--system] [--all] [--list] [--filesharing] Retrieves a list of installed applications. --system prints out preinstalled system apps. --all prints all apps, including system, user, and hidden apps. --list only prints bundle ID, bundle name and version number. --filesharing only prints apps which enable documents sharing. ios launch [--wait] Launch app with the bundleID on the device. Get your bundle ID from the apps command. --wait keeps the connection open if you want logs. ios kill ( | --pid= | --process=) [options] Kill app with the specified bundleID, process id, or process name on the device. - ios runtest [--bundle-id=] [--test-runner-bundle-id=] [--xctest-config=] [--log-output=] [--test-to-run=]... [--test-to-skip=]... [--env=]... [options] Run a XCUITest. If you provide only bundle-id go-ios will try to dynamically create test-runner-bundle-id and xctest-config. + ios runtest [--bundle-id=] [--test-runner-bundle-id=] [--xctest-config=] [--log-output=] [--test-to-run=]... [--test-to-skip=]... [--env=]... [options] Run a XCUITest. If you provide only bundle-id go-ios will try to dynamically create test-runner-bundle-id and xctest-config. > If you provide '-' as log output, it prints resuts to stdout. > To be able to filter for tests to run or skip, use one argument per test selector. Example: runtest --test-to-run=(TestTarget.)TestClass/testMethod --test-to-run=(TestTarget.)TestClass/testMethod (the value for 'TestTarget' is optional) > The method name can also be omitted and in this case all tests of the specified class are run @@ -242,7 +243,7 @@ The commands work as following: > On systems with System Integrity Protection enabled the argument '--pair-record-path' is required as we can not access the default path for the pair record > This command needs to be executed with admin privileges. > (On MacOS the process 'remoted' must be paused before starting a tunnel is possible 'sudo pkill -SIGSTOP remoted', and 'sudo pkill -SIGCONT remoted' to resume) - ios tunnel ls List currently started tunnels + ios tunnel ls List currently started tunnels ios devmode (enable | get) [--enable-post-restart] [options] Enable developer mode on the device or check if it is enabled. Can also completely finalize developer mode setup after device is restarted. `, version) @@ -605,6 +606,15 @@ The commands work as following: if b { lat, _ := arguments.String("--lat") lon, _ := arguments.String("--lon") + + if device.SupportsRsd() { + server, err := instruments.NewLocationSimulationService(device) + exitIfError("failed to create location simulation service:", err) + + startLocationSimulation(server, lat, lon) + return + } + setLocation(device, lat, lon) return } @@ -1730,6 +1740,29 @@ func setLocationGPX(device ios.DeviceEntry, gpxFilePath string) { exitIfError("Setting location failed with", err) } +func startLocationSimulation(service *instruments.LocationSimulationService, lat string, lon string) { + latitude, err := strconv.ParseFloat(lat, 64) + exitIfError("location simulation failed to parse lat", err) + + longitude, err := strconv.ParseFloat(lon, 64) + exitIfError("location simulation failed to parse lon", err) + + err = service.StartSimulateLocation(latitude, longitude) + exitIfError("location simulation failed to start with", err) + + defer stopLocationSimulation(service) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c +} + +func stopLocationSimulation(service *instruments.LocationSimulationService) { + err := service.StopSimulateLocation() + if err != nil { + exitIfError("location simulation failed to stop with", err) + } +} + func resetLocation(device ios.DeviceEntry) { err := simlocation.ResetLocation(device) exitIfError("Resetting location failed with", err)