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
pymobiledevice3/cli/restore.py
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import contextlib
|
|
3
3
|
import logging
|
|
4
|
-
import os
|
|
5
4
|
import plistlib
|
|
6
5
|
import tempfile
|
|
7
6
|
import traceback
|
|
8
|
-
from collections.abc import
|
|
7
|
+
from collections.abc import Iterator
|
|
9
8
|
from pathlib import Path
|
|
10
|
-
from typing import IO,
|
|
9
|
+
from typing import IO, Annotated, Optional
|
|
11
10
|
from zipfile import ZipFile
|
|
12
11
|
|
|
13
12
|
import click
|
|
14
13
|
import IPython
|
|
15
14
|
import requests
|
|
15
|
+
import typer
|
|
16
16
|
from pygments import formatters, highlight, lexers
|
|
17
|
+
from typer_injector import Depends, InjectingTyper
|
|
17
18
|
|
|
18
19
|
from pymobiledevice3 import usbmux
|
|
19
|
-
from pymobiledevice3.cli.cli_common import print_json, prompt_selection
|
|
20
|
+
from pymobiledevice3.cli.cli_common import is_invoked_for_completion, print_json, prompt_selection
|
|
20
21
|
from pymobiledevice3.exceptions import ConnectionFailedError, ConnectionFailedToUsbmuxdError, IncorrectModeError
|
|
21
22
|
from pymobiledevice3.irecv import IRecv
|
|
22
23
|
from pymobiledevice3.lockdown import LockdownClient, create_using_usbmux
|
|
@@ -26,107 +27,132 @@ from pymobiledevice3.restore.restore import Restore
|
|
|
26
27
|
from pymobiledevice3.services.diagnostics import DiagnosticsService
|
|
27
28
|
from pymobiledevice3.utils import file_download
|
|
28
29
|
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
29
33
|
SHELL_USAGE = """
|
|
30
34
|
# use `irecv` variable to access Restore mode API
|
|
31
35
|
# for example:
|
|
32
36
|
print(irecv.getenv('build-version'))
|
|
33
37
|
"""
|
|
38
|
+
IPSWME_API = "https://api.ipsw.me/v4/device/"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
cli = InjectingTyper(
|
|
42
|
+
name="restore",
|
|
43
|
+
help="Restore/erase IPSWs, fetch blobs, and manage devices in Recovery/DFU.",
|
|
44
|
+
no_args_is_help=True,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def device_dependency(
|
|
49
|
+
ecid: Annotated[
|
|
50
|
+
Optional[str],
|
|
51
|
+
typer.Option(
|
|
52
|
+
help="Target device ECID; defaults to the first connected USB device or waits for Recovery/DFU.",
|
|
53
|
+
),
|
|
54
|
+
] = None,
|
|
55
|
+
) -> Optional[Device]:
|
|
56
|
+
if is_invoked_for_completion():
|
|
57
|
+
# prevent lockdown connection establishment when in autocomplete mode
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
logger.debug("searching among connected devices via lockdownd")
|
|
61
|
+
devices = [dev for dev in usbmux.list_devices() if dev.connection_type == "USB"]
|
|
62
|
+
if len(devices) > 1:
|
|
63
|
+
raise click.ClickException("Multiple device detected")
|
|
64
|
+
try:
|
|
65
|
+
for device in devices:
|
|
66
|
+
try:
|
|
67
|
+
lockdown = create_using_usbmux(serial=device.serial, connection_type="USB")
|
|
68
|
+
except (ConnectionFailedError, IncorrectModeError):
|
|
69
|
+
continue
|
|
70
|
+
if (ecid is None) or (lockdown.ecid == ecid):
|
|
71
|
+
logger.debug("found device")
|
|
72
|
+
return Device(lockdown=lockdown)
|
|
73
|
+
else:
|
|
74
|
+
continue
|
|
75
|
+
except ConnectionFailedToUsbmuxdError:
|
|
76
|
+
pass
|
|
34
77
|
|
|
35
|
-
logger
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
click.Option(('device', '--ecid'), type=click.INT, callback=self.device),
|
|
44
|
-
click.Option(('verbosity', '-v', '--verbose'), count=True, callback=set_verbosity, expose_value=False),
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
def device(ctx, param, value) -> Optional[Union[LockdownClient, IRecv]]:
|
|
49
|
-
if '_PYMOBILEDEVICE3_COMPLETE' in os.environ:
|
|
50
|
-
# prevent lockdown connection establishment when in autocomplete mode
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
ecid = value
|
|
54
|
-
logger.debug('searching among connected devices via lockdownd')
|
|
55
|
-
devices = [dev for dev in usbmux.list_devices() if dev.connection_type == 'USB']
|
|
56
|
-
if len(devices) > 1:
|
|
57
|
-
raise click.ClickException('Multiple device detected')
|
|
58
|
-
try:
|
|
59
|
-
for device in devices:
|
|
60
|
-
try:
|
|
61
|
-
lockdown = create_using_usbmux(serial=device.serial, connection_type='USB')
|
|
62
|
-
except (ConnectionFailedError, IncorrectModeError):
|
|
63
|
-
continue
|
|
64
|
-
if (ecid is None) or (lockdown.ecid == value):
|
|
65
|
-
logger.debug('found device')
|
|
66
|
-
return lockdown
|
|
67
|
-
else:
|
|
68
|
-
continue
|
|
69
|
-
except ConnectionFailedToUsbmuxdError:
|
|
70
|
-
pass
|
|
71
|
-
|
|
72
|
-
logger.debug('waiting for device to be available in Recovery mode')
|
|
73
|
-
return IRecv(ecid=ecid)
|
|
78
|
+
logger.debug("waiting for device to be available in Recovery mode")
|
|
79
|
+
return Device(irecv=IRecv(ecid=ecid))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
DeviceDep = Annotated[
|
|
83
|
+
Device,
|
|
84
|
+
Depends(device_dependency),
|
|
85
|
+
]
|
|
74
86
|
|
|
75
87
|
|
|
76
88
|
@contextlib.contextmanager
|
|
77
|
-
def tempzip_download_ctx(url: str) ->
|
|
89
|
+
def tempzip_download_ctx(url: str) -> Iterator[ZipFile]:
|
|
78
90
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
79
|
-
tmpzip = Path(tmpdir) / url.split(
|
|
91
|
+
tmpzip = Path(tmpdir) / url.split("/")[-1]
|
|
80
92
|
file_download(url, tmpzip)
|
|
81
93
|
yield ZipFile(tmpzip)
|
|
82
94
|
|
|
83
95
|
|
|
84
96
|
@contextlib.contextmanager
|
|
85
|
-
def zipfile_ctx(path: str) ->
|
|
97
|
+
def zipfile_ctx(path: str) -> Iterator[ZipFile]:
|
|
86
98
|
yield ZipFile(path)
|
|
87
99
|
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
def ipsw_ctx_dependency(
|
|
102
|
+
device: DeviceDep,
|
|
103
|
+
ipsw: Annotated[
|
|
104
|
+
Optional[str],
|
|
105
|
+
typer.Option(
|
|
106
|
+
"--ipsw",
|
|
107
|
+
"-i",
|
|
108
|
+
help="Path or URL to an IPSW. If omitted, choose a signed build interactively.",
|
|
109
|
+
),
|
|
110
|
+
] = None,
|
|
111
|
+
) -> contextlib.AbstractContextManager[ZipFile]:
|
|
112
|
+
if ipsw and not ipsw.startswith(("http://", "https://")):
|
|
113
|
+
return zipfile_ctx(ipsw)
|
|
95
114
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
url = ipsw
|
|
116
|
+
if url is None:
|
|
117
|
+
url = query_ipswme(device.product_type)
|
|
118
|
+
return tempzip_download_ctx(url)
|
|
100
119
|
|
|
101
|
-
url = value
|
|
102
|
-
if url is None:
|
|
103
|
-
url = query_ipswme(ctx.params['device'].product_type)
|
|
104
|
-
return tempzip_download_ctx(url)
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return plistlib.load(value)
|
|
121
|
+
IPSWCtxDep = Annotated[
|
|
122
|
+
contextlib.AbstractContextManager[ZipFile],
|
|
123
|
+
Depends(ipsw_ctx_dependency),
|
|
124
|
+
]
|
|
111
125
|
|
|
112
126
|
|
|
113
|
-
def
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
def tss_dependency(
|
|
128
|
+
tss: Annotated[
|
|
129
|
+
Optional[Path],
|
|
130
|
+
typer.Option(help="Path to SHSH blob plist to use for signing requests."),
|
|
131
|
+
] = None,
|
|
132
|
+
) -> None:
|
|
133
|
+
if tss is None:
|
|
134
|
+
return
|
|
135
|
+
with tss.open("rb") as tss_file:
|
|
136
|
+
return plistlib.load(tss_file)
|
|
119
137
|
|
|
120
138
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
lockdown = device
|
|
126
|
-
elif isinstance(device, IRecv):
|
|
127
|
-
irecv = device
|
|
128
|
-
device = Device(lockdown=lockdown, irecv=irecv)
|
|
139
|
+
TSSDep = Annotated[
|
|
140
|
+
Optional[dict],
|
|
141
|
+
Depends(tss_dependency),
|
|
142
|
+
]
|
|
129
143
|
|
|
144
|
+
|
|
145
|
+
def query_ipswme(identifier: str) -> str:
|
|
146
|
+
resp = requests.get(IPSWME_API + identifier, headers={"Accept": "application/json"})
|
|
147
|
+
firmwares = resp.json()["firmwares"]
|
|
148
|
+
display_list = [f"{entry['version']}: {entry['buildid']}" for entry in firmwares if entry["signed"]]
|
|
149
|
+
idx = prompt_selection(display_list, "Choose version", idx=True)
|
|
150
|
+
return firmwares[idx]["url"]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def restore_update_task(
|
|
154
|
+
device: Device, ipsw: ZipFile, tss: Optional[dict], erase: bool, ignore_fdr: bool
|
|
155
|
+
) -> None:
|
|
130
156
|
behavior = Behavior.Update
|
|
131
157
|
if erase:
|
|
132
158
|
behavior = Behavior.Erase
|
|
@@ -139,98 +165,88 @@ async def restore_update_task(device: Device, ipsw: ZipFile, tss: Optional[IO],
|
|
|
139
165
|
raise
|
|
140
166
|
|
|
141
167
|
|
|
142
|
-
@
|
|
143
|
-
def
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
@cli.group()
|
|
148
|
-
def restore() -> None:
|
|
149
|
-
""" Restore an IPSW or access device in recovery mode """
|
|
150
|
-
pass
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@restore.command('shell', cls=Command)
|
|
154
|
-
def restore_shell(device):
|
|
155
|
-
""" create an IPython shell for interacting with iBoot """
|
|
168
|
+
@cli.command("shell")
|
|
169
|
+
def restore_shell(device: DeviceDep) -> None:
|
|
170
|
+
"""create an IPython shell for interacting with iBoot"""
|
|
156
171
|
IPython.embed(
|
|
157
|
-
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style=
|
|
172
|
+
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")),
|
|
158
173
|
user_ns={
|
|
159
|
-
|
|
160
|
-
}
|
|
174
|
+
"irecv": device,
|
|
175
|
+
},
|
|
176
|
+
)
|
|
161
177
|
|
|
162
178
|
|
|
163
|
-
@
|
|
164
|
-
def restore_enter(device):
|
|
165
|
-
"""
|
|
179
|
+
@cli.command("enter")
|
|
180
|
+
def restore_enter(device: DeviceDep) -> None:
|
|
181
|
+
"""enter Recovery mode"""
|
|
166
182
|
if isinstance(device, LockdownClient):
|
|
167
183
|
device.enter_recovery()
|
|
168
184
|
|
|
169
185
|
|
|
170
|
-
@
|
|
171
|
-
def restore_exit():
|
|
172
|
-
"""
|
|
186
|
+
@cli.command("exit")
|
|
187
|
+
def restore_exit() -> None:
|
|
188
|
+
"""exit Recovery mode"""
|
|
173
189
|
irecv = IRecv()
|
|
174
190
|
irecv.set_autoboot(True)
|
|
175
191
|
irecv.reboot()
|
|
176
192
|
|
|
177
193
|
|
|
178
|
-
@
|
|
179
|
-
def restore_restart(device):
|
|
180
|
-
"""
|
|
181
|
-
if
|
|
182
|
-
with DiagnosticsService(device) as diagnostics:
|
|
194
|
+
@cli.command("restart")
|
|
195
|
+
def restore_restart(device: DeviceDep) -> None:
|
|
196
|
+
"""restarts device"""
|
|
197
|
+
if device.is_lockdown:
|
|
198
|
+
with DiagnosticsService(device.lockdown) as diagnostics:
|
|
183
199
|
diagnostics.restart()
|
|
184
200
|
else:
|
|
185
|
-
device.reboot()
|
|
201
|
+
device.irecv.reboot()
|
|
186
202
|
|
|
187
203
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
""" query SHSH blobs """
|
|
192
|
-
lockdown = None
|
|
193
|
-
irecv = None
|
|
194
|
-
if isinstance(device, LockdownClient):
|
|
195
|
-
lockdown = device
|
|
196
|
-
elif isinstance(device, IRecv):
|
|
197
|
-
irecv = device
|
|
198
|
-
|
|
199
|
-
device = Device(lockdown=lockdown, irecv=irecv)
|
|
204
|
+
async def restore_tss_task(
|
|
205
|
+
device: Device, ipsw_ctx: contextlib.AbstractContextManager[ZipFile], out: Optional[IO]
|
|
206
|
+
) -> None:
|
|
200
207
|
with ipsw_ctx as ipsw:
|
|
201
|
-
tss = Recovery(ipsw, device).fetch_tss_record()
|
|
208
|
+
tss = await Recovery(ipsw, device).fetch_tss_record()
|
|
202
209
|
if out:
|
|
203
210
|
plistlib.dump(tss, out)
|
|
204
211
|
print_json(tss)
|
|
205
212
|
|
|
206
213
|
|
|
207
|
-
@
|
|
208
|
-
def
|
|
209
|
-
"""
|
|
210
|
-
|
|
214
|
+
@cli.command("tss")
|
|
215
|
+
def restore_tss(device: DeviceDep, ipsw_ctx: IPSWCtxDep, out: Optional[Path] = None) -> None:
|
|
216
|
+
"""query SHSH blobs"""
|
|
217
|
+
with out.open("wb") if out else contextlib.nullcontext() as out_file:
|
|
218
|
+
asyncio.run(restore_tss_task(device, ipsw_ctx, out_file), debug=True)
|
|
211
219
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
lockdown = None
|
|
215
|
-
irecv = None
|
|
216
|
-
if isinstance(device, LockdownClient):
|
|
217
|
-
lockdown = device
|
|
218
|
-
elif isinstance(device, IRecv):
|
|
219
|
-
irecv = device
|
|
220
|
-
device = Device(lockdown=lockdown, irecv=irecv)
|
|
220
|
+
|
|
221
|
+
async def restore_ramdisk_task(device: Device, ipsw_ctx: contextlib.AbstractContextManager[ZipFile]) -> None:
|
|
221
222
|
with ipsw_ctx as ipsw:
|
|
222
|
-
Recovery(ipsw, device
|
|
223
|
+
await Recovery(ipsw, device).boot_ramdisk()
|
|
223
224
|
|
|
224
225
|
|
|
225
|
-
@
|
|
226
|
-
|
|
227
|
-
@click.option('--ignore-fdr', is_flag=True, help='only establish an FDR service connection, but don\'t proxy any '
|
|
228
|
-
'traffic')
|
|
229
|
-
def restore_update(device: Device, ipsw_ctx: Generator, tss: IO, erase: bool, ignore_fdr: bool) -> None:
|
|
226
|
+
@cli.command("ramdisk")
|
|
227
|
+
def restore_ramdisk(device: DeviceDep, ipsw_ctx: IPSWCtxDep) -> None:
|
|
230
228
|
"""
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
Boot only the update ramdisk without performing a restore (IPSW path or URL accepted).
|
|
230
|
+
"""
|
|
231
|
+
asyncio.run(restore_ramdisk_task(device, ipsw_ctx), debug=True)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@cli.command("update")
|
|
235
|
+
def restore_update(
|
|
236
|
+
device: DeviceDep,
|
|
237
|
+
ipsw_ctx: IPSWCtxDep,
|
|
238
|
+
tss: TSSDep,
|
|
239
|
+
erase: Annotated[
|
|
240
|
+
bool,
|
|
241
|
+
typer.Option(help="Erase and restore (factory reset) instead of updating in place."),
|
|
242
|
+
] = False,
|
|
243
|
+
ignore_fdr: Annotated[
|
|
244
|
+
bool,
|
|
245
|
+
typer.Option(help="Connect to the FDR service only (debug mode; no traffic proxying)."),
|
|
246
|
+
] = False,
|
|
247
|
+
) -> None:
|
|
248
|
+
"""
|
|
249
|
+
Update or restore the device using an IPSW (local path or URL).
|
|
234
250
|
"""
|
|
235
251
|
with ipsw_ctx as ipsw:
|
|
236
252
|
asyncio.run(restore_update_task(device, ipsw, tss, erase, ignore_fdr), debug=True)
|
|
@@ -1,80 +1,90 @@
|
|
|
1
|
-
from
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated, Literal
|
|
2
3
|
|
|
3
|
-
import click
|
|
4
4
|
import IPython
|
|
5
|
+
import typer
|
|
6
|
+
from typer_injector import InjectingTyper
|
|
5
7
|
|
|
6
|
-
from pymobiledevice3.cli.cli_common import
|
|
7
|
-
from pymobiledevice3.lockdown import LockdownClient
|
|
8
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
|
|
8
9
|
from pymobiledevice3.services.springboard import SpringBoardServicesService
|
|
9
10
|
|
|
10
|
-
SHELL_USAGE =
|
|
11
|
+
SHELL_USAGE = """
|
|
11
12
|
Use `service` to access the service features
|
|
12
|
-
|
|
13
|
+
"""
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
cli = InjectingTyper(
|
|
17
|
+
name="springboard",
|
|
18
|
+
help="Interact with SpringBoard UI (icons, wallpapers, orientation, shell).",
|
|
19
|
+
no_args_is_help=True,
|
|
20
|
+
)
|
|
21
|
+
state_cli = InjectingTyper(
|
|
22
|
+
name="state",
|
|
23
|
+
help="Icon state operations.",
|
|
24
|
+
no_args_is_help=True,
|
|
25
|
+
)
|
|
26
|
+
cli.add_typer(state_cli)
|
|
18
27
|
|
|
19
28
|
|
|
20
|
-
@
|
|
21
|
-
def
|
|
22
|
-
"""
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@springboard.group()
|
|
27
|
-
def state():
|
|
28
|
-
""" icons state options """
|
|
29
|
-
pass
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@state.command('get', cls=Command)
|
|
33
|
-
def state_get(service_provider: LockdownClient):
|
|
34
|
-
""" get icon state """
|
|
29
|
+
@state_cli.command("get")
|
|
30
|
+
def state_get(service_provider: ServiceProviderDep) -> None:
|
|
31
|
+
"""Fetch the current icon layout/state."""
|
|
35
32
|
print_json(SpringBoardServicesService(lockdown=service_provider).get_icon_state())
|
|
36
33
|
|
|
37
34
|
|
|
38
|
-
@
|
|
39
|
-
def springboard_shell(service_provider:
|
|
40
|
-
"""
|
|
35
|
+
@cli.command("shell")
|
|
36
|
+
def springboard_shell(service_provider: ServiceProviderDep) -> None:
|
|
37
|
+
"""Open an IPython shell bound to SpringBoardServicesService."""
|
|
41
38
|
service = SpringBoardServicesService(lockdown=service_provider)
|
|
42
39
|
IPython.embed(
|
|
43
40
|
header=SHELL_USAGE,
|
|
44
41
|
user_ns={
|
|
45
|
-
|
|
46
|
-
}
|
|
42
|
+
"service": service,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
47
45
|
|
|
48
46
|
|
|
49
|
-
@
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
""" get application's icon """
|
|
54
|
-
out.write(SpringBoardServicesService(lockdown=service_provider).get_icon_pngdata(bundle_id))
|
|
47
|
+
@cli.command("icon")
|
|
48
|
+
def springboard_icon(service_provider: ServiceProviderDep, bundle_id: str, out: Path) -> None:
|
|
49
|
+
"""Save an app's icon PNG to the given path."""
|
|
50
|
+
out.write_bytes(SpringBoardServicesService(lockdown=service_provider).get_icon_pngdata(bundle_id))
|
|
55
51
|
|
|
56
52
|
|
|
57
|
-
@
|
|
58
|
-
def springboard_orientation(service_provider:
|
|
59
|
-
"""
|
|
53
|
+
@cli.command("orientation")
|
|
54
|
+
def springboard_orientation(service_provider: ServiceProviderDep) -> None:
|
|
55
|
+
"""Print current screen orientation."""
|
|
60
56
|
print(SpringBoardServicesService(lockdown=service_provider).get_interface_orientation())
|
|
61
57
|
|
|
62
58
|
|
|
63
|
-
@
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
59
|
+
@cli.command("wallpaper-home-screen")
|
|
60
|
+
def springboard_wallpaper_home_screen(service_provider: ServiceProviderDep, out: Path) -> None:
|
|
61
|
+
"""Save the homescreen wallpaper PNG to the given path."""
|
|
62
|
+
out.write_bytes(SpringBoardServicesService(lockdown=service_provider).get_wallpaper_pngdata())
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@cli.command("wallpaper-preview-image")
|
|
66
|
+
def springboard_wallpaper_preview_image(
|
|
67
|
+
service_provider: ServiceProviderDep,
|
|
68
|
+
wallpaper_name: Literal["homescreen", "lockscreen"],
|
|
69
|
+
out: Path,
|
|
70
|
+
reload: Annotated[
|
|
71
|
+
bool,
|
|
72
|
+
typer.Option(
|
|
73
|
+
"--reload",
|
|
74
|
+
"-r",
|
|
75
|
+
help="reload icon state before fetching image",
|
|
76
|
+
),
|
|
77
|
+
] = False,
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Save the preview image for the homescreen or lockscreen wallpaper (optionally reload state first)."""
|
|
77
80
|
with SpringBoardServicesService(lockdown=service_provider) as springboard_service:
|
|
78
81
|
if reload:
|
|
79
82
|
springboard_service.reload_icon_state()
|
|
80
|
-
out.
|
|
83
|
+
out.write_bytes(springboard_service.get_wallpaper_preview_image(wallpaper_name))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@cli.command("homescreen-icon-metrics")
|
|
87
|
+
def springboard_homescreen_icon_metrics(service_provider: ServiceProviderDep) -> None:
|
|
88
|
+
"""Print homescreen icon spacing/metrics."""
|
|
89
|
+
with SpringBoardServicesService(lockdown=service_provider) as springboard_service:
|
|
90
|
+
print_json(springboard_service.get_homescreen_icon_metrics())
|