pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- misc/understanding_idevice_protocol_layers.md +15 -10
- pymobiledevice3/__main__.py +317 -127
- pymobiledevice3/_version.py +22 -4
- pymobiledevice3/bonjour.py +358 -113
- pymobiledevice3/ca.py +253 -16
- pymobiledevice3/cli/activation.py +31 -23
- pymobiledevice3/cli/afc.py +49 -40
- pymobiledevice3/cli/amfi.py +16 -21
- pymobiledevice3/cli/apps.py +87 -42
- pymobiledevice3/cli/backup.py +160 -90
- pymobiledevice3/cli/bonjour.py +44 -40
- pymobiledevice3/cli/cli_common.py +204 -198
- pymobiledevice3/cli/companion_proxy.py +14 -14
- pymobiledevice3/cli/crash.py +105 -56
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +42 -0
- pymobiledevice3/cli/lockdown.py +108 -103
- pymobiledevice3/cli/mounter.py +158 -99
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +45 -24
- pymobiledevice3/cli/power_assertion.py +18 -17
- pymobiledevice3/cli/processes.py +17 -23
- pymobiledevice3/cli/profile.py +165 -109
- pymobiledevice3/cli/provision.py +35 -34
- pymobiledevice3/cli/remote.py +217 -129
- pymobiledevice3/cli/restore.py +159 -143
- pymobiledevice3/cli/springboard.py +63 -53
- pymobiledevice3/cli/syslog.py +193 -86
- pymobiledevice3/cli/usbmux.py +73 -33
- pymobiledevice3/cli/version.py +5 -7
- pymobiledevice3/cli/webinspector.py +376 -214
- pymobiledevice3/common.py +3 -1
- pymobiledevice3/exceptions.py +182 -58
- pymobiledevice3/irecv.py +52 -53
- pymobiledevice3/irecv_devices.py +1489 -464
- pymobiledevice3/lockdown.py +473 -275
- pymobiledevice3/lockdown_service_provider.py +15 -8
- pymobiledevice3/osu/os_utils.py +27 -9
- pymobiledevice3/osu/posix_util.py +34 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +102 -21
- pymobiledevice3/remote/common.py +8 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
- pymobiledevice3/remote/core_device/file_service.py +53 -23
- pymobiledevice3/remote/remote_service_discovery.py +79 -45
- pymobiledevice3/remote/remotexpc.py +73 -44
- pymobiledevice3/remote/tunnel_service.py +442 -317
- pymobiledevice3/remote/utils.py +14 -13
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +20 -16
- pymobiledevice3/resources/notifications.txt +144 -0
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +110 -21
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +59 -12
- pymobiledevice3/restore/fdr.py +46 -48
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +163 -0
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +151 -151
- pymobiledevice3/restore/restore.py +562 -544
- pymobiledevice3/restore/restore_options.py +131 -110
- pymobiledevice3/restore/restored_client.py +51 -31
- pymobiledevice3/restore/tss.py +385 -267
- pymobiledevice3/service_connection.py +252 -59
- pymobiledevice3/services/accessibilityaudit.py +202 -120
- pymobiledevice3/services/afc.py +962 -365
- pymobiledevice3/services/amfi.py +24 -30
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +71 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +101 -79
- pymobiledevice3/services/diagnostics.py +973 -967
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +9 -8
- pymobiledevice3/services/house_arrest.py +16 -15
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/installation_proxy.py +173 -81
- pymobiledevice3/services/lockdown_service.py +20 -10
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +147 -64
- pymobiledevice3/services/mobile_config.py +331 -294
- pymobiledevice3/services/mobile_image_mounter.py +141 -113
- pymobiledevice3/services/mobilebackup2.py +203 -145
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +134 -74
- pymobiledevice3/services/pcapd.py +314 -302
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +21 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +15 -12
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +11 -11
- pymobiledevice3/services/web_protocol/automation_session.py +251 -239
- pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +114 -111
- pymobiledevice3/services/web_protocol/element.py +124 -111
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
- pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
- pymobiledevice3/services/web_protocol/switch_to.py +30 -27
- pymobiledevice3/services/webinspector.py +189 -155
- pymobiledevice3/tcp_forwarder.py +87 -69
- pymobiledevice3/tunneld/__init__.py +0 -0
- pymobiledevice3/tunneld/api.py +63 -0
- pymobiledevice3/tunneld/server.py +603 -0
- pymobiledevice3/usbmux.py +198 -147
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
- pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
- pymobiledevice3/cli/developer.py +0 -1215
- pymobiledevice3/cli/diagnostics.py +0 -99
- pymobiledevice3/tunneld.py +0 -524
- pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import posixpath
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import IO, Annotated, Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import typer
|
|
10
|
+
from typer_injector import InjectingTyper
|
|
11
|
+
|
|
12
|
+
from pymobiledevice3.cli.cli_common import RSDServiceProviderDep, print_json
|
|
13
|
+
from pymobiledevice3.lockdown import create_using_usbmux
|
|
14
|
+
from pymobiledevice3.remote.core_device.app_service import AppServiceService
|
|
15
|
+
from pymobiledevice3.remote.core_device.device_info import DeviceInfoService
|
|
16
|
+
from pymobiledevice3.remote.core_device.diagnostics_service import DiagnosticsServiceService
|
|
17
|
+
from pymobiledevice3.remote.core_device.file_service import APPLE_DOMAIN_DICT, FileServiceService
|
|
18
|
+
from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
|
|
19
|
+
from pymobiledevice3.services.crash_reports import CrashReportsManager
|
|
20
|
+
from pymobiledevice3.utils import try_decode
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
cli = InjectingTyper(
|
|
26
|
+
name="core-device",
|
|
27
|
+
help="Access DeveloperDiskImage services (files, processes, app launch, diagnostics).",
|
|
28
|
+
no_args_is_help=True,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def core_device_list_directory_task(
|
|
33
|
+
service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str
|
|
34
|
+
) -> None:
|
|
35
|
+
async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain], identifier) as file_service:
|
|
36
|
+
print_json(await file_service.retrieve_directory_list(path))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@cli.command("list-directory")
|
|
40
|
+
def core_device_list_directory(
|
|
41
|
+
service_provider: RSDServiceProviderDep,
|
|
42
|
+
domain: Annotated[
|
|
43
|
+
str,
|
|
44
|
+
typer.Argument(click_type=click.Choice(APPLE_DOMAIN_DICT)),
|
|
45
|
+
],
|
|
46
|
+
path: str,
|
|
47
|
+
identifier: Annotated[str, typer.Option()] = "",
|
|
48
|
+
) -> None:
|
|
49
|
+
"""List directory contents for a given domain/path."""
|
|
50
|
+
asyncio.run(core_device_list_directory_task(service_provider, domain, path, identifier))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def core_device_read_file_task(
|
|
54
|
+
service_provider: RemoteServiceDiscoveryService,
|
|
55
|
+
domain: str,
|
|
56
|
+
path: str,
|
|
57
|
+
identifier: str,
|
|
58
|
+
output: Optional[IO],
|
|
59
|
+
) -> None:
|
|
60
|
+
async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain], identifier) as file_service:
|
|
61
|
+
buf = await file_service.retrieve_file(path)
|
|
62
|
+
if output is not None:
|
|
63
|
+
output.write(buf)
|
|
64
|
+
else:
|
|
65
|
+
print(try_decode(buf))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@cli.command("read-file")
|
|
69
|
+
def core_device_read_file(
|
|
70
|
+
service_provider: RSDServiceProviderDep,
|
|
71
|
+
domain: Annotated[
|
|
72
|
+
str,
|
|
73
|
+
typer.Argument(click_type=click.Choice(APPLE_DOMAIN_DICT)),
|
|
74
|
+
],
|
|
75
|
+
path: str,
|
|
76
|
+
*,
|
|
77
|
+
identifier: Annotated[str, typer.Option()] = "",
|
|
78
|
+
output: Annotated[
|
|
79
|
+
Path,
|
|
80
|
+
typer.Option("--output", "-o"),
|
|
81
|
+
],
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Read a file from a domain/path to stdout or --output."""
|
|
84
|
+
with output.open("wb") as output_file:
|
|
85
|
+
asyncio.run(core_device_read_file_task(service_provider, domain, path, identifier, output_file))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def core_device_propose_empty_file_task(
|
|
89
|
+
service_provider: RemoteServiceDiscoveryService,
|
|
90
|
+
domain: str,
|
|
91
|
+
path: str,
|
|
92
|
+
identifier: str,
|
|
93
|
+
file_permissions: int,
|
|
94
|
+
uid: int,
|
|
95
|
+
gid: int,
|
|
96
|
+
creation_time: int,
|
|
97
|
+
last_modification_time: int,
|
|
98
|
+
) -> None:
|
|
99
|
+
async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain], identifier) as file_service:
|
|
100
|
+
await file_service.propose_empty_file(path, file_permissions, uid, gid, creation_time, last_modification_time)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@cli.command("propose-empty-file")
|
|
104
|
+
def core_device_propose_empty_file(
|
|
105
|
+
service_provider: RSDServiceProviderDep,
|
|
106
|
+
domain: Annotated[
|
|
107
|
+
str,
|
|
108
|
+
typer.Argument(click_type=click.Choice(APPLE_DOMAIN_DICT)),
|
|
109
|
+
],
|
|
110
|
+
path: str,
|
|
111
|
+
identifier: Annotated[str, typer.Option()] = "",
|
|
112
|
+
file_permissions: Annotated[int, typer.Option()] = 0o644,
|
|
113
|
+
uid: Annotated[int, typer.Option()] = 501,
|
|
114
|
+
gid: Annotated[int, typer.Option()] = 501,
|
|
115
|
+
creation_time: Annotated[Optional[int], typer.Option()] = None,
|
|
116
|
+
last_modification_time: Annotated[Optional[int], typer.Option()] = None,
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Create an empty file at the given domain/path with custom permissions/owner/timestamps."""
|
|
119
|
+
asyncio.run(
|
|
120
|
+
core_device_propose_empty_file_task(
|
|
121
|
+
service_provider,
|
|
122
|
+
domain,
|
|
123
|
+
path,
|
|
124
|
+
identifier,
|
|
125
|
+
file_permissions,
|
|
126
|
+
uid,
|
|
127
|
+
gid,
|
|
128
|
+
creation_time if creation_time is not None else int(time.time()),
|
|
129
|
+
last_modification_time if last_modification_time is not None else int(time.time()),
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
async def core_device_list_launch_application_task(
|
|
135
|
+
service_provider: RemoteServiceDiscoveryService,
|
|
136
|
+
bundle_identifier: str,
|
|
137
|
+
argument: list[str],
|
|
138
|
+
kill_existing: bool,
|
|
139
|
+
suspended: bool,
|
|
140
|
+
env: dict[str, str],
|
|
141
|
+
) -> None:
|
|
142
|
+
async with AppServiceService(service_provider) as app_service:
|
|
143
|
+
print_json(await app_service.launch_application(bundle_identifier, argument, kill_existing, suspended, env))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@cli.command("launch-application")
|
|
147
|
+
def core_device_launch_application(
|
|
148
|
+
service_provider: RSDServiceProviderDep,
|
|
149
|
+
bundle_identifier: str,
|
|
150
|
+
argument: list[str],
|
|
151
|
+
kill_existing: Annotated[
|
|
152
|
+
bool,
|
|
153
|
+
typer.Option(help="Whether to kill an existing instance of this process"),
|
|
154
|
+
] = True,
|
|
155
|
+
suspended: Annotated[bool, typer.Option(help="Same as WaitForDebugger")] = False,
|
|
156
|
+
env: Annotated[
|
|
157
|
+
Optional[list[str]],
|
|
158
|
+
typer.Option(
|
|
159
|
+
help="Environment variable to pass to process given as key=value (can be specified multiple times)"
|
|
160
|
+
),
|
|
161
|
+
] = None,
|
|
162
|
+
) -> None:
|
|
163
|
+
"""Launch an app; optionally kill existing, wait for debugger, or set env vars."""
|
|
164
|
+
asyncio.run(
|
|
165
|
+
core_device_list_launch_application_task(
|
|
166
|
+
service_provider,
|
|
167
|
+
bundle_identifier,
|
|
168
|
+
list(argument),
|
|
169
|
+
kill_existing,
|
|
170
|
+
suspended,
|
|
171
|
+
dict(var.split("=", 1) for var in env or ()),
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def core_device_list_processes_task(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
177
|
+
async with AppServiceService(service_provider) as app_service:
|
|
178
|
+
print_json(await app_service.list_processes())
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@cli.command("list-processes")
|
|
182
|
+
def core_device_list_processes(service_provider: RSDServiceProviderDep) -> None:
|
|
183
|
+
"""List running processes via CoreDevice."""
|
|
184
|
+
asyncio.run(core_device_list_processes_task(service_provider))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
async def core_device_uninstall_app_task(
|
|
188
|
+
service_provider: RemoteServiceDiscoveryService, bundle_identifier: str
|
|
189
|
+
) -> None:
|
|
190
|
+
async with AppServiceService(service_provider) as app_service:
|
|
191
|
+
await app_service.uninstall_app(bundle_identifier)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@cli.command("uninstall")
|
|
195
|
+
def core_device_uninstall_app(service_provider: RSDServiceProviderDep, bundle_identifier: str) -> None:
|
|
196
|
+
"""Uninstall an app by bundle identifier via CoreDevice."""
|
|
197
|
+
asyncio.run(core_device_uninstall_app_task(service_provider, bundle_identifier))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
async def core_device_send_signal_to_process_task(
|
|
201
|
+
service_provider: RemoteServiceDiscoveryService, pid: int, signal: int
|
|
202
|
+
) -> None:
|
|
203
|
+
async with AppServiceService(service_provider) as app_service:
|
|
204
|
+
print_json(await app_service.send_signal_to_process(pid, signal))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@cli.command("send-signal-to-process")
|
|
208
|
+
def core_device_send_signal_to_process(service_provider: RSDServiceProviderDep, pid: int, signal: int) -> None:
|
|
209
|
+
"""Send signal to process"""
|
|
210
|
+
asyncio.run(core_device_send_signal_to_process_task(service_provider, pid, signal))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def core_device_get_device_info_task(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
214
|
+
async with DeviceInfoService(service_provider) as app_service:
|
|
215
|
+
print_json(await app_service.get_device_info())
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@cli.command("get-device-info")
|
|
219
|
+
def core_device_get_device_info(service_provider: RSDServiceProviderDep) -> None:
|
|
220
|
+
"""Get device information"""
|
|
221
|
+
asyncio.run(core_device_get_device_info_task(service_provider))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
async def core_device_get_display_info_task(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
225
|
+
async with DeviceInfoService(service_provider) as app_service:
|
|
226
|
+
print_json(await app_service.get_display_info())
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@cli.command("get-display-info")
|
|
230
|
+
def core_device_get_display_info(service_provider: RSDServiceProviderDep) -> None:
|
|
231
|
+
"""Get display information"""
|
|
232
|
+
asyncio.run(core_device_get_display_info_task(service_provider))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
async def core_device_query_mobilegestalt_task(service_provider: RemoteServiceDiscoveryService, key: list[str]) -> None:
|
|
236
|
+
"""Query MobileGestalt"""
|
|
237
|
+
async with DeviceInfoService(service_provider) as app_service:
|
|
238
|
+
print_json(await app_service.query_mobilegestalt(key))
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@cli.command("query-mobilegestalt")
|
|
242
|
+
def core_device_query_mobilegestalt(service_provider: RSDServiceProviderDep, key: list[str]) -> None:
|
|
243
|
+
"""Query MobileGestalt"""
|
|
244
|
+
asyncio.run(core_device_query_mobilegestalt_task(service_provider, key))
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
async def core_device_get_lockstate_task(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
248
|
+
async with DeviceInfoService(service_provider) as app_service:
|
|
249
|
+
print_json(await app_service.get_lockstate())
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@cli.command("get-lockstate")
|
|
253
|
+
def core_device_get_lockstate(service_provider: RSDServiceProviderDep) -> None:
|
|
254
|
+
"""Get lockstate"""
|
|
255
|
+
asyncio.run(core_device_get_lockstate_task(service_provider))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
async def core_device_list_apps_task(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
259
|
+
async with AppServiceService(service_provider) as app_service:
|
|
260
|
+
print_json(await app_service.list_apps())
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@cli.command("list-apps")
|
|
264
|
+
def core_device_list_apps(service_provider: RSDServiceProviderDep) -> None:
|
|
265
|
+
"""Get application list"""
|
|
266
|
+
asyncio.run(core_device_list_apps_task(service_provider))
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
async def core_device_sysdiagnose_task(service_provider: RemoteServiceDiscoveryService, output: Path) -> None:
|
|
270
|
+
async with DiagnosticsServiceService(service_provider) as service:
|
|
271
|
+
response = await service.capture_sysdiagnose(False)
|
|
272
|
+
logger.info(f"Operation response: {response}")
|
|
273
|
+
if output.is_dir():
|
|
274
|
+
output /= response.preferred_filename
|
|
275
|
+
logger.info(f"Downloading sysdiagnose to: {output}")
|
|
276
|
+
|
|
277
|
+
# get the file over lockdownd which is WAYYY faster
|
|
278
|
+
lockdown = create_using_usbmux(service_provider.udid)
|
|
279
|
+
with CrashReportsManager(lockdown) as crash_reports_manager:
|
|
280
|
+
crash_reports_manager.afc.pull(
|
|
281
|
+
posixpath.join(f"/DiagnosticLogs/sysdiagnose/{response.preferred_filename}"), str(output)
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@cli.command("sysdiagnose")
|
|
286
|
+
def core_device_sysdiagnose(
|
|
287
|
+
service_provider: RSDServiceProviderDep,
|
|
288
|
+
output: Annotated[
|
|
289
|
+
Path,
|
|
290
|
+
typer.Argument(dir_okay=True, file_okay=True, exists=True),
|
|
291
|
+
],
|
|
292
|
+
) -> None:
|
|
293
|
+
"""Execute sysdiagnose and fetch the output file"""
|
|
294
|
+
asyncio.run(core_device_sysdiagnose_task(service_provider, output))
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import plistlib
|
|
4
|
+
import signal
|
|
5
|
+
import struct
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Annotated, Optional
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from packaging.version import Version
|
|
13
|
+
from plumbum import local
|
|
14
|
+
from typer_injector import InjectingTyper
|
|
15
|
+
|
|
16
|
+
from pymobiledevice3.cli.cli_common import RSDServiceProviderDep, ServiceProviderDep, print_json
|
|
17
|
+
from pymobiledevice3.exceptions import RSDRequiredError
|
|
18
|
+
from pymobiledevice3.lockdown import create_using_usbmux
|
|
19
|
+
from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
|
|
20
|
+
from pymobiledevice3.services.debugserver_applist import DebugServerAppList
|
|
21
|
+
from pymobiledevice3.services.installation_proxy import InstallationProxyService
|
|
22
|
+
from pymobiledevice3.tcp_forwarder import LockdownTcpForwarder
|
|
23
|
+
|
|
24
|
+
DEBUGSERVER_CONNECTION_STEPS = """
|
|
25
|
+
Follow the following connections steps from LLDB:
|
|
26
|
+
|
|
27
|
+
(lldb) platform select remote-ios
|
|
28
|
+
(lldb) target create /path/to/local/application.app
|
|
29
|
+
(lldb) script lldb.target.module[0].SetPlatformFileSpec(lldb.SBFileSpec('/private/var/containers/Bundle/Application/<APP-UUID>/application.app'))
|
|
30
|
+
(lldb) process connect connect://[{host}]:{port} <-- ACTUAL CONNECTION DETAILS!
|
|
31
|
+
(lldb) process launch
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
cli = InjectingTyper(
|
|
39
|
+
name="debugserver",
|
|
40
|
+
help="Start and drive debugserver sessions (RSD for iOS 17+, usbmux for older).",
|
|
41
|
+
no_args_is_help=True,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@cli.command("applist")
|
|
46
|
+
def debugserver_applist(service_provider: ServiceProviderDep) -> None:
|
|
47
|
+
"""Print the debugserver applist XML for the device."""
|
|
48
|
+
print_json(DebugServerAppList(service_provider).get())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@cli.command("start-server")
|
|
52
|
+
def debugserver_start_server(service_provider: ServiceProviderDep, local_port: Optional[int] = None) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Start debugserver and print the LLDB connect string.
|
|
55
|
+
|
|
56
|
+
- For iOS < 17, you must forward to a local port (--local-port).
|
|
57
|
+
- For iOS >= 17, if connected over RSD, the remote host:port is printed for LLDB.
|
|
58
|
+
Connect quickly with your own LLDB client using the printed steps.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
if Version(service_provider.product_version) < Version("17.0"):
|
|
62
|
+
service_name = "com.apple.debugserver.DVTSecureSocketProxy"
|
|
63
|
+
else:
|
|
64
|
+
service_name = "com.apple.internal.dt.remote.debugproxy"
|
|
65
|
+
|
|
66
|
+
if local_port is not None:
|
|
67
|
+
print(DEBUGSERVER_CONNECTION_STEPS.format(host="127.0.0.1", port=local_port))
|
|
68
|
+
print("Started port forwarding. Press Ctrl-C to close this shell when done")
|
|
69
|
+
sys.stdout.flush()
|
|
70
|
+
LockdownTcpForwarder(service_provider, local_port, service_name).start()
|
|
71
|
+
elif Version(service_provider.product_version) >= Version("17.0"):
|
|
72
|
+
if not isinstance(service_provider, RemoteServiceDiscoveryService):
|
|
73
|
+
raise RSDRequiredError(service_provider.identifier)
|
|
74
|
+
debugserver_port = service_provider.get_service_port(service_name)
|
|
75
|
+
print(DEBUGSERVER_CONNECTION_STEPS.format(host=service_provider.service.address[0], port=debugserver_port))
|
|
76
|
+
else:
|
|
77
|
+
print("local_port is required for iOS < 17.0")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@cli.command("lldb")
|
|
81
|
+
def debugserver_lldb(
|
|
82
|
+
service_provider: RSDServiceProviderDep,
|
|
83
|
+
xcodeproj_path: Annotated[
|
|
84
|
+
Path,
|
|
85
|
+
typer.Argument(exists=True, file_okay=False, dir_okay=True),
|
|
86
|
+
],
|
|
87
|
+
configuration: Annotated[
|
|
88
|
+
str,
|
|
89
|
+
typer.Option(help="Build configuration to invoke (e.g., Debug or Release)."),
|
|
90
|
+
] = "Debug",
|
|
91
|
+
lldb_command: Annotated[
|
|
92
|
+
str,
|
|
93
|
+
typer.Option(help="Path to the lldb executable to run."),
|
|
94
|
+
] = "lldb",
|
|
95
|
+
launch: Annotated[
|
|
96
|
+
bool,
|
|
97
|
+
typer.Option(help="Automatically launch the app after attaching."),
|
|
98
|
+
] = False,
|
|
99
|
+
breakpoints: Annotated[
|
|
100
|
+
Optional[list[str]],
|
|
101
|
+
typer.Option("--break", "-b", help="Add multiple startup breakpoints"),
|
|
102
|
+
] = None,
|
|
103
|
+
user_commands: Annotated[
|
|
104
|
+
Optional[list[str]],
|
|
105
|
+
typer.Option("--command", "-c", help="Additional commands to run at startup"),
|
|
106
|
+
] = None,
|
|
107
|
+
) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Automate lldb launch for a given xcodeproj.
|
|
110
|
+
|
|
111
|
+
\b
|
|
112
|
+
This will:
|
|
113
|
+
- Build the given xcodeproj
|
|
114
|
+
- Install it
|
|
115
|
+
- Start a debugserver attached to it
|
|
116
|
+
- Place breakpoints if given any
|
|
117
|
+
- Launch the application if requested
|
|
118
|
+
- Execute any additional commands if requested
|
|
119
|
+
- Switch to lldb shell
|
|
120
|
+
"""
|
|
121
|
+
if Version(service_provider.product_version) < Version("17.0"):
|
|
122
|
+
logger.error("lldb is only supported on iOS >= 17.0")
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
commands = []
|
|
126
|
+
with local.cwd(xcodeproj_path.parent):
|
|
127
|
+
logger.info(f"Building {xcodeproj_path} for {configuration} configuration")
|
|
128
|
+
local["xcodebuild"]["-configuration", configuration, "build"]()
|
|
129
|
+
local_app = next(iter(Path(f"build/{configuration}-iphoneos").glob("*.app")))
|
|
130
|
+
logger.info(f"Using app: {local_app}")
|
|
131
|
+
|
|
132
|
+
info_plist_path = local_app / "Info.plist"
|
|
133
|
+
info_plist = plistlib.loads(info_plist_path.read_bytes())
|
|
134
|
+
bundle_identifier = info_plist["CFBundleIdentifier"]
|
|
135
|
+
logger.info(f"Bundle identifier: {bundle_identifier}")
|
|
136
|
+
|
|
137
|
+
commands.append("platform select remote-ios")
|
|
138
|
+
commands.append(f'target create "{local_app.absolute()}"')
|
|
139
|
+
|
|
140
|
+
with InstallationProxyService(create_using_usbmux()) as installation_proxy:
|
|
141
|
+
logger.info("Installing app")
|
|
142
|
+
installation_proxy.install_from_local(local_app)
|
|
143
|
+
remote_path = installation_proxy.get_apps(bundle_identifiers=[bundle_identifier])[bundle_identifier]["Path"]
|
|
144
|
+
logger.info(f"Remote path: {remote_path}")
|
|
145
|
+
|
|
146
|
+
commands.append(f'script lldb.target.module[0].SetPlatformFileSpec(lldb.SBFileSpec("{remote_path}"))')
|
|
147
|
+
|
|
148
|
+
debugserver_port = service_provider.get_service_port("com.apple.internal.dt.remote.debugproxy")
|
|
149
|
+
|
|
150
|
+
# Add connection and launch commands
|
|
151
|
+
commands.append(f"process connect connect://[{service_provider.service.address[0]}]:{debugserver_port}")
|
|
152
|
+
|
|
153
|
+
if breakpoints:
|
|
154
|
+
for bp in breakpoints:
|
|
155
|
+
commands.append(f'breakpoint set -n "{bp}"')
|
|
156
|
+
|
|
157
|
+
if launch:
|
|
158
|
+
commands.append("process launch")
|
|
159
|
+
|
|
160
|
+
if user_commands:
|
|
161
|
+
# Add user commands
|
|
162
|
+
commands += user_commands
|
|
163
|
+
|
|
164
|
+
logger.info("Starting lldb with automated setup and connection")
|
|
165
|
+
|
|
166
|
+
# Works only on unix-based systems, so keep these imports here
|
|
167
|
+
import fcntl
|
|
168
|
+
import pty
|
|
169
|
+
import select as select_module
|
|
170
|
+
import termios
|
|
171
|
+
import tty
|
|
172
|
+
|
|
173
|
+
master, slave = pty.openpty()
|
|
174
|
+
|
|
175
|
+
process = None # Initialize process variable for signal handler
|
|
176
|
+
|
|
177
|
+
# Copy terminal size from the current terminal to PTY
|
|
178
|
+
def resize_pty() -> None:
|
|
179
|
+
"""Update PTY size to match current terminal size"""
|
|
180
|
+
size = struct.unpack(
|
|
181
|
+
"HHHH", fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0))
|
|
182
|
+
)
|
|
183
|
+
fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack("HHHH", *size))
|
|
184
|
+
# Send SIGWINCH to the child process to notify it of the resize
|
|
185
|
+
if process is not None and process.poll() is None:
|
|
186
|
+
process.send_signal(signal.SIGWINCH)
|
|
187
|
+
|
|
188
|
+
# Initial resize
|
|
189
|
+
resize_pty()
|
|
190
|
+
|
|
191
|
+
# Set up signal handler for window resize
|
|
192
|
+
def handle_sigwinch(signum, frame):
|
|
193
|
+
resize_pty()
|
|
194
|
+
|
|
195
|
+
old_sigwinch_handler = signal.signal(signal.SIGWINCH, handle_sigwinch)
|
|
196
|
+
|
|
197
|
+
# Save original terminal settings
|
|
198
|
+
old_tty = termios.tcgetattr(sys.stdin)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
# Set TERM environment variable to enable colors
|
|
202
|
+
env = os.environ.copy()
|
|
203
|
+
env["TERM"] = os.environ.get("TERM", "xterm-256color")
|
|
204
|
+
|
|
205
|
+
process = subprocess.Popen([lldb_command], stdin=slave, stdout=slave, stderr=slave, env=env)
|
|
206
|
+
os.close(slave)
|
|
207
|
+
|
|
208
|
+
# Put terminal in raw mode for proper interaction
|
|
209
|
+
tty.setraw(sys.stdin.fileno())
|
|
210
|
+
# Send all commands through stdin
|
|
211
|
+
for command in commands:
|
|
212
|
+
os.write(master, (command + "\n").encode())
|
|
213
|
+
|
|
214
|
+
# Now redirect stdin from the terminal to lldb so user can interact
|
|
215
|
+
while True:
|
|
216
|
+
rlist, _, _ = select_module.select([sys.stdin, master], [], [])
|
|
217
|
+
|
|
218
|
+
if sys.stdin in rlist:
|
|
219
|
+
# User typed something
|
|
220
|
+
data = os.read(sys.stdin.fileno(), 1024)
|
|
221
|
+
if not data:
|
|
222
|
+
break
|
|
223
|
+
os.write(master, data)
|
|
224
|
+
|
|
225
|
+
if master in rlist:
|
|
226
|
+
# lldb has output
|
|
227
|
+
try:
|
|
228
|
+
data = os.read(master, 1024)
|
|
229
|
+
if not data:
|
|
230
|
+
break
|
|
231
|
+
os.write(sys.stdout.fileno(), data)
|
|
232
|
+
except OSError:
|
|
233
|
+
break
|
|
234
|
+
except (KeyboardInterrupt, OSError):
|
|
235
|
+
pass
|
|
236
|
+
finally:
|
|
237
|
+
# Restore terminal settings
|
|
238
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
|
|
239
|
+
# Restore original SIGWINCH handler
|
|
240
|
+
signal.signal(signal.SIGWINCH, old_sigwinch_handler)
|
|
241
|
+
os.close(master)
|
|
242
|
+
if process is not None:
|
|
243
|
+
process.terminate()
|
|
244
|
+
process.wait()
|