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
|
@@ -5,11 +5,19 @@ import uuid
|
|
|
5
5
|
from contextlib import contextmanager, suppress
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Union
|
|
8
9
|
|
|
9
|
-
from pymobiledevice3.exceptions import
|
|
10
|
-
|
|
10
|
+
from pymobiledevice3.exceptions import (
|
|
11
|
+
AfcException,
|
|
12
|
+
AfcFileNotFoundError,
|
|
13
|
+
ConnectionTerminatedError,
|
|
14
|
+
LockdownError,
|
|
15
|
+
MissingValueError,
|
|
16
|
+
PyMobileDevice3Exception,
|
|
17
|
+
)
|
|
11
18
|
from pymobiledevice3.lockdown import LockdownClient
|
|
12
|
-
from pymobiledevice3.
|
|
19
|
+
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
20
|
+
from pymobiledevice3.services.afc import AFC_LOCK_EX, AFC_LOCK_UN, AfcError, AfcService
|
|
13
21
|
from pymobiledevice3.services.device_link import DeviceLink
|
|
14
22
|
from pymobiledevice3.services.installation_proxy import InstallationProxyService
|
|
15
23
|
from pymobiledevice3.services.lockdown_service import LockdownService
|
|
@@ -18,34 +26,44 @@ from pymobiledevice3.services.springboard import SpringBoardServicesService
|
|
|
18
26
|
|
|
19
27
|
SUPPORTED_VERSIONS = [2.0, 2.1]
|
|
20
28
|
ITUNES_FILES = [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
"ApertureAlbumPrefs",
|
|
30
|
+
"IC-Info.sidb",
|
|
31
|
+
"IC-Info.sidv",
|
|
32
|
+
"PhotosFolderAlbums",
|
|
33
|
+
"PhotosFolderName",
|
|
34
|
+
"PhotosFolderPrefs",
|
|
35
|
+
"VoiceMemos.plist",
|
|
36
|
+
"iPhotoAlbumPrefs",
|
|
37
|
+
"iTunesApplicationIDs",
|
|
38
|
+
"iTunesPrefs",
|
|
39
|
+
"iTunesPrefs.plist",
|
|
24
40
|
]
|
|
25
|
-
NP_SYNC_WILL_START =
|
|
26
|
-
NP_SYNC_DID_START =
|
|
27
|
-
NP_SYNC_LOCK_REQUEST =
|
|
28
|
-
NP_SYNC_DID_FINISH =
|
|
41
|
+
NP_SYNC_WILL_START = "com.apple.itunes-mobdev.syncWillStart"
|
|
42
|
+
NP_SYNC_DID_START = "com.apple.itunes-mobdev.syncDidStart"
|
|
43
|
+
NP_SYNC_LOCK_REQUEST = "com.apple.itunes-mobdev.syncLockRequest"
|
|
44
|
+
NP_SYNC_DID_FINISH = "com.apple.itunes-mobdev.syncDidFinish"
|
|
29
45
|
|
|
30
46
|
|
|
31
47
|
class Mobilebackup2Service(LockdownService):
|
|
32
|
-
SERVICE_NAME =
|
|
33
|
-
RSD_SERVICE_NAME =
|
|
48
|
+
SERVICE_NAME = "com.apple.mobilebackup2"
|
|
49
|
+
RSD_SERVICE_NAME = "com.apple.mobilebackup2.shim.remote"
|
|
34
50
|
|
|
35
|
-
def __init__(self, lockdown:
|
|
51
|
+
def __init__(self, lockdown: LockdownServiceProvider) -> None:
|
|
36
52
|
if isinstance(lockdown, LockdownClient):
|
|
37
53
|
super().__init__(lockdown, self.SERVICE_NAME, include_escrow_bag=True)
|
|
38
54
|
else:
|
|
39
55
|
super().__init__(lockdown, self.RSD_SERVICE_NAME, include_escrow_bag=True)
|
|
40
56
|
|
|
41
57
|
@property
|
|
42
|
-
def will_encrypt(self):
|
|
58
|
+
def will_encrypt(self) -> bool:
|
|
43
59
|
try:
|
|
44
|
-
return self.lockdown.get_value(
|
|
60
|
+
return self.lockdown.get_value("com.apple.mobile.backup", "WillEncrypt")
|
|
45
61
|
except LockdownError:
|
|
46
62
|
return False
|
|
47
63
|
|
|
48
|
-
def backup(
|
|
64
|
+
def backup(
|
|
65
|
+
self, full: bool = True, backup_directory: Union[str, Path] = ".", progress_callback=lambda x: None
|
|
66
|
+
) -> None:
|
|
49
67
|
"""
|
|
50
68
|
Backup a device.
|
|
51
69
|
:param full: Whether to do a full backup. If full is True, any previous backup attempts will be discarded.
|
|
@@ -57,42 +75,58 @@ class Mobilebackup2Service(LockdownService):
|
|
|
57
75
|
device_directory = backup_directory / self.lockdown.udid
|
|
58
76
|
device_directory.mkdir(exist_ok=True, mode=0o755, parents=True)
|
|
59
77
|
|
|
60
|
-
with
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
with (
|
|
79
|
+
self.device_link(backup_directory) as dl,
|
|
80
|
+
NotificationProxyService(self.lockdown) as notification_proxy,
|
|
81
|
+
AfcService(self.lockdown) as afc,
|
|
82
|
+
self._backup_lock(afc, notification_proxy),
|
|
83
|
+
):
|
|
84
|
+
# Initialize Info.plist
|
|
85
|
+
info_plist = self.init_mobile_backup_factory_info(afc)
|
|
86
|
+
with open(device_directory / "Info.plist", "wb") as fd:
|
|
87
|
+
plistlib.dump(info_plist, fd)
|
|
68
88
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
# Initialize Status.plist file if doesn't exist.
|
|
90
|
+
status_path = device_directory / "Status.plist"
|
|
91
|
+
current_date = datetime.now()
|
|
92
|
+
current_date = current_date.replace(tzinfo=None)
|
|
93
|
+
if full or not status_path.exists():
|
|
94
|
+
with open(device_directory / "Status.plist", "wb") as fd:
|
|
95
|
+
plistlib.dump(
|
|
96
|
+
{
|
|
97
|
+
"BackupState": "new",
|
|
98
|
+
"Date": current_date,
|
|
99
|
+
"IsFullBackup": full,
|
|
100
|
+
"Version": "3.3",
|
|
101
|
+
"SnapshotState": "finished",
|
|
102
|
+
"UUID": str(uuid.uuid4()).upper(),
|
|
103
|
+
},
|
|
104
|
+
fd,
|
|
105
|
+
fmt=plistlib.FMT_BINARY,
|
|
106
|
+
)
|
|
83
107
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
# Create Manifest.plist if doesn't exist.
|
|
109
|
+
manifest_path = device_directory / "Manifest.plist"
|
|
110
|
+
if full:
|
|
111
|
+
manifest_path.unlink(missing_ok=True)
|
|
112
|
+
(device_directory / "Manifest.plist").touch()
|
|
89
113
|
|
|
90
|
-
|
|
91
|
-
|
|
114
|
+
dl.send_process_message({"MessageName": "Backup", "TargetIdentifier": self.lockdown.udid})
|
|
115
|
+
dl.dl_loop(progress_callback)
|
|
92
116
|
|
|
93
|
-
def restore(
|
|
94
|
-
|
|
95
|
-
|
|
117
|
+
def restore(
|
|
118
|
+
self,
|
|
119
|
+
backup_directory=".",
|
|
120
|
+
system: bool = False,
|
|
121
|
+
reboot: bool = True,
|
|
122
|
+
copy: bool = True,
|
|
123
|
+
settings: bool = True,
|
|
124
|
+
remove: bool = False,
|
|
125
|
+
password: str = "",
|
|
126
|
+
source: str = "",
|
|
127
|
+
progress_callback=lambda x: None,
|
|
128
|
+
skip_apps: bool = False,
|
|
129
|
+
):
|
|
96
130
|
"""
|
|
97
131
|
Restore a previous backup to the device.
|
|
98
132
|
:param backup_directory: Path of the backup directory.
|
|
@@ -104,42 +138,55 @@ class Mobilebackup2Service(LockdownService):
|
|
|
104
138
|
:param password: Password of the backup if it is encrypted.
|
|
105
139
|
:param source: Identifier of device to restore its backup.
|
|
106
140
|
:param progress_callback: Function to be called as the backup progresses.
|
|
141
|
+
:param skip_apps: Do not trigger re-installation of apps after restore.
|
|
107
142
|
The function shall receive the current percentage of the progress as a parameter.
|
|
108
143
|
"""
|
|
109
144
|
backup_directory = Path(backup_directory)
|
|
110
145
|
source = source if source else self.lockdown.udid
|
|
111
146
|
self._assert_backup_exists(backup_directory, source)
|
|
112
147
|
|
|
113
|
-
with
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
148
|
+
with (
|
|
149
|
+
self.device_link(backup_directory) as dl,
|
|
150
|
+
NotificationProxyService(self.lockdown) as notification_proxy,
|
|
151
|
+
AfcService(self.lockdown) as afc,
|
|
152
|
+
self._backup_lock(afc, notification_proxy),
|
|
153
|
+
):
|
|
154
|
+
manifest_plist_path = backup_directory / source / "Manifest.plist"
|
|
155
|
+
with open(manifest_plist_path, "rb") as fd:
|
|
156
|
+
manifest = plistlib.load(fd)
|
|
157
|
+
is_encrypted = manifest.get("IsEncrypted", False)
|
|
158
|
+
options = {
|
|
159
|
+
"RestoreShouldReboot": reboot,
|
|
160
|
+
"RestoreDontCopyBackup": not copy,
|
|
161
|
+
"RestorePreserveSettings": settings,
|
|
162
|
+
"RestoreSystemFiles": system,
|
|
163
|
+
"RemoveItemsNotRestored": remove,
|
|
164
|
+
}
|
|
165
|
+
if is_encrypted:
|
|
166
|
+
if password:
|
|
167
|
+
options["Password"] = password
|
|
168
|
+
else:
|
|
169
|
+
self.logger.error("Backup is encrypted, please supply password.")
|
|
170
|
+
return
|
|
171
|
+
dl.send_process_message({
|
|
172
|
+
"MessageName": "Restore",
|
|
173
|
+
"TargetIdentifier": self.lockdown.udid,
|
|
174
|
+
"SourceIdentifier": source,
|
|
175
|
+
"Options": options,
|
|
176
|
+
})
|
|
141
177
|
|
|
142
|
-
|
|
178
|
+
if not skip_apps:
|
|
179
|
+
# Write /iTunesRestore/RestoreApplications.plist so that the device will start
|
|
180
|
+
# restoring applications once the rest of the restore process is finished
|
|
181
|
+
info_plist_path = backup_directory / source / "Info.plist"
|
|
182
|
+
applications = plistlib.loads(info_plist_path.read_bytes()).get("Applications")
|
|
183
|
+
if applications is not None:
|
|
184
|
+
afc.makedirs("/iTunesRestore")
|
|
185
|
+
afc.set_file_contents("/iTunesRestore/RestoreApplications.plist", plistlib.dumps(applications))
|
|
186
|
+
|
|
187
|
+
dl.dl_loop(progress_callback)
|
|
188
|
+
|
|
189
|
+
def info(self, backup_directory=".", source: str = "") -> str:
|
|
143
190
|
"""
|
|
144
191
|
Get information about a backup.
|
|
145
192
|
:param backup_directory: Path of the backup directory.
|
|
@@ -149,14 +196,14 @@ class Mobilebackup2Service(LockdownService):
|
|
|
149
196
|
backup_dir = Path(backup_directory)
|
|
150
197
|
self._assert_backup_exists(backup_dir, source if source else self.lockdown.udid)
|
|
151
198
|
with self.device_link(backup_dir) as dl:
|
|
152
|
-
message = {
|
|
199
|
+
message = {"MessageName": "Info", "TargetIdentifier": self.lockdown.udid}
|
|
153
200
|
if source:
|
|
154
|
-
message[
|
|
201
|
+
message["SourceIdentifier"] = source
|
|
155
202
|
dl.send_process_message(message)
|
|
156
203
|
result = dl.dl_loop()
|
|
157
204
|
return result
|
|
158
205
|
|
|
159
|
-
def list(self, backup_directory=
|
|
206
|
+
def list(self, backup_directory=".", source: str = "") -> str:
|
|
160
207
|
"""
|
|
161
208
|
List the files in the last backup.
|
|
162
209
|
:param backup_directory: Path of the backup directory.
|
|
@@ -168,12 +215,14 @@ class Mobilebackup2Service(LockdownService):
|
|
|
168
215
|
self._assert_backup_exists(backup_dir, source)
|
|
169
216
|
with self.device_link(backup_dir) as dl:
|
|
170
217
|
dl.send_process_message({
|
|
171
|
-
|
|
218
|
+
"MessageName": "List",
|
|
219
|
+
"TargetIdentifier": self.lockdown.udid,
|
|
220
|
+
"SourceIdentifier": source,
|
|
172
221
|
})
|
|
173
222
|
result = dl.dl_loop()
|
|
174
223
|
return result
|
|
175
224
|
|
|
176
|
-
def unback(self, backup_directory=
|
|
225
|
+
def unback(self, backup_directory=".", password: str = "", source: str = "") -> None:
|
|
177
226
|
"""
|
|
178
227
|
Unpack a complete backup to its device hierarchy.
|
|
179
228
|
:param backup_directory: Path of the backup directory.
|
|
@@ -183,16 +232,17 @@ class Mobilebackup2Service(LockdownService):
|
|
|
183
232
|
backup_dir = Path(backup_directory)
|
|
184
233
|
self._assert_backup_exists(backup_dir, source if source else self.lockdown.udid)
|
|
185
234
|
with self.device_link(backup_dir) as dl:
|
|
186
|
-
message = {
|
|
235
|
+
message = {"MessageName": "Unback", "TargetIdentifier": self.lockdown.udid}
|
|
187
236
|
if source:
|
|
188
|
-
message[
|
|
237
|
+
message["SourceIdentifier"] = source
|
|
189
238
|
if password:
|
|
190
|
-
message[
|
|
239
|
+
message["Password"] = password
|
|
191
240
|
dl.send_process_message(message)
|
|
192
241
|
dl.dl_loop()
|
|
193
242
|
|
|
194
|
-
def extract(
|
|
195
|
-
|
|
243
|
+
def extract(
|
|
244
|
+
self, domain_name: str, relative_path: str, backup_directory=".", password: str = "", source: str = ""
|
|
245
|
+
) -> None:
|
|
196
246
|
"""
|
|
197
247
|
Extract a file from a previous backup.
|
|
198
248
|
:param domain_name: File's domain name, e.g., SystemPreferencesDomain or HomeDomain.
|
|
@@ -205,17 +255,19 @@ class Mobilebackup2Service(LockdownService):
|
|
|
205
255
|
self._assert_backup_exists(backup_dir, source if source else self.lockdown.udid)
|
|
206
256
|
with self.device_link(backup_dir) as dl:
|
|
207
257
|
message = {
|
|
208
|
-
|
|
209
|
-
|
|
258
|
+
"MessageName": "Extract",
|
|
259
|
+
"TargetIdentifier": self.lockdown.udid,
|
|
260
|
+
"DomainName": domain_name,
|
|
261
|
+
"RelativePath": relative_path,
|
|
210
262
|
}
|
|
211
263
|
if source:
|
|
212
|
-
message[
|
|
264
|
+
message["SourceIdentifier"] = source
|
|
213
265
|
if password:
|
|
214
|
-
message[
|
|
266
|
+
message["Password"] = password
|
|
215
267
|
dl.send_process_message(message)
|
|
216
268
|
dl.dl_loop()
|
|
217
269
|
|
|
218
|
-
def change_password(self, backup_directory=
|
|
270
|
+
def change_password(self, backup_directory=".", old: str = "", new: str = "") -> None:
|
|
219
271
|
"""
|
|
220
272
|
Change backup password.
|
|
221
273
|
:param backup_directory: Backups directory.
|
|
@@ -223,22 +275,21 @@ class Mobilebackup2Service(LockdownService):
|
|
|
223
275
|
:param new: New password. Omit when disabling backup encryption.
|
|
224
276
|
"""
|
|
225
277
|
with self.device_link(Path(backup_directory)) as dl:
|
|
226
|
-
message = {
|
|
278
|
+
message = {"MessageName": "ChangePassword", "TargetIdentifier": self.lockdown.udid}
|
|
227
279
|
if old:
|
|
228
|
-
message[
|
|
280
|
+
message["OldPassword"] = old
|
|
229
281
|
if new:
|
|
230
|
-
message[
|
|
282
|
+
message["NewPassword"] = new
|
|
231
283
|
dl.send_process_message(message)
|
|
232
284
|
dl.dl_loop()
|
|
233
285
|
|
|
234
|
-
def erase_device(self, backup_directory=
|
|
286
|
+
def erase_device(self, backup_directory=".") -> None:
|
|
235
287
|
"""
|
|
236
288
|
Erase the device.
|
|
237
289
|
"""
|
|
238
|
-
with suppress(ConnectionTerminatedError):
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
dl.dl_loop()
|
|
290
|
+
with suppress(ConnectionTerminatedError), self.device_link(Path(backup_directory)) as dl:
|
|
291
|
+
dl.send_process_message({"MessageName": "EraseDevice", "TargetIdentifier": self.lockdown.udid})
|
|
292
|
+
dl.dl_loop()
|
|
242
293
|
|
|
243
294
|
def version_exchange(self, dl: DeviceLink, local_versions=None) -> None:
|
|
244
295
|
"""
|
|
@@ -249,89 +300,96 @@ class Mobilebackup2Service(LockdownService):
|
|
|
249
300
|
if local_versions is None:
|
|
250
301
|
local_versions = SUPPORTED_VERSIONS
|
|
251
302
|
dl.send_process_message({
|
|
252
|
-
|
|
253
|
-
|
|
303
|
+
"MessageName": "Hello",
|
|
304
|
+
"SupportedProtocolVersions": local_versions,
|
|
254
305
|
})
|
|
255
306
|
reply = dl.receive_message()
|
|
256
|
-
assert reply[0] ==
|
|
257
|
-
assert reply[1][
|
|
307
|
+
assert reply[0] == "DLMessageProcessMessage" and reply[1]["ErrorCode"] == 0
|
|
308
|
+
assert reply[1]["ProtocolVersion"] in local_versions
|
|
258
309
|
|
|
259
310
|
def init_mobile_backup_factory_info(self, afc: AfcService):
|
|
260
311
|
with InstallationProxyService(self.lockdown) as ip, SpringBoardServicesService(self.lockdown) as sbs:
|
|
261
312
|
root_node = self.lockdown.get_value()
|
|
262
|
-
itunes_settings = self.lockdown.get_value(domain=
|
|
263
|
-
|
|
313
|
+
itunes_settings = self.lockdown.get_value(domain="com.apple.iTunes")
|
|
314
|
+
try:
|
|
315
|
+
min_itunes_version = self.lockdown.get_value("com.apple.mobile.iTunes", "MinITunesVersion")
|
|
316
|
+
except MissingValueError:
|
|
317
|
+
# iPadOS may not contain this value. See:
|
|
318
|
+
# https://github.com/doronz88/pymobiledevice3/issues/1332
|
|
319
|
+
min_itunes_version = "10.0.1"
|
|
264
320
|
app_dict = {}
|
|
265
321
|
installed_apps = []
|
|
266
|
-
apps = ip.browse(
|
|
267
|
-
|
|
322
|
+
apps = ip.browse(
|
|
323
|
+
options={"ApplicationType": "User"},
|
|
324
|
+
attributes=["CFBundleIdentifier", "ApplicationSINF", "iTunesMetadata"],
|
|
325
|
+
)
|
|
268
326
|
for app in apps:
|
|
269
|
-
bundle_id = app[
|
|
327
|
+
bundle_id = app["CFBundleIdentifier"]
|
|
270
328
|
if bundle_id:
|
|
271
329
|
installed_apps.append(bundle_id)
|
|
272
|
-
if app.get(
|
|
330
|
+
if app.get("iTunesMetadata", False) and app.get("ApplicationSINF", False):
|
|
273
331
|
app_dict[bundle_id] = {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
332
|
+
"ApplicationSINF": app["ApplicationSINF"],
|
|
333
|
+
"iTunesMetadata": app["iTunesMetadata"],
|
|
334
|
+
"PlaceholderIcon": sbs.get_icon_pngdata(bundle_id),
|
|
277
335
|
}
|
|
278
336
|
|
|
279
337
|
files = {}
|
|
280
338
|
for file in ITUNES_FILES:
|
|
281
339
|
try:
|
|
282
|
-
data_buf = afc.get_file_contents(
|
|
340
|
+
data_buf = afc.get_file_contents("/iTunes_Control/iTunes/" + file)
|
|
283
341
|
except AfcFileNotFoundError:
|
|
284
342
|
pass
|
|
285
343
|
else:
|
|
286
344
|
files[file] = data_buf
|
|
287
345
|
|
|
288
346
|
ret = {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
347
|
+
"iTunes Version": min_itunes_version if min_itunes_version else "10.0.1",
|
|
348
|
+
"iTunes Files": files,
|
|
349
|
+
"Unique Identifier": self.lockdown.udid.upper(),
|
|
350
|
+
"Target Type": "Device",
|
|
351
|
+
"Target Identifier": root_node["UniqueDeviceID"],
|
|
352
|
+
"Serial Number": root_node["SerialNumber"],
|
|
353
|
+
"Product Version": root_node["ProductVersion"],
|
|
354
|
+
"Product Type": root_node["ProductType"],
|
|
355
|
+
"Installed Applications": installed_apps,
|
|
356
|
+
"GUID": uuid.uuid4().bytes,
|
|
357
|
+
"Display Name": root_node["DeviceName"],
|
|
358
|
+
"Device Name": root_node["DeviceName"],
|
|
359
|
+
"Build Version": root_node["BuildVersion"],
|
|
360
|
+
"Applications": app_dict,
|
|
303
361
|
}
|
|
304
362
|
|
|
305
|
-
if
|
|
306
|
-
ret[
|
|
307
|
-
if
|
|
308
|
-
ret[
|
|
309
|
-
if
|
|
310
|
-
ret[
|
|
311
|
-
if
|
|
312
|
-
ret[
|
|
363
|
+
if "IntegratedCircuitCardIdentity" in root_node:
|
|
364
|
+
ret["ICCID"] = root_node["IntegratedCircuitCardIdentity"]
|
|
365
|
+
if "InternationalMobileEquipmentIdentity" in root_node:
|
|
366
|
+
ret["IMEI"] = root_node["InternationalMobileEquipmentIdentity"]
|
|
367
|
+
if "MobileEquipmentIdentifier" in root_node:
|
|
368
|
+
ret["MEID"] = root_node["MobileEquipmentIdentifier"]
|
|
369
|
+
if "PhoneNumber" in root_node:
|
|
370
|
+
ret["Phone Number"] = root_node["PhoneNumber"]
|
|
313
371
|
|
|
314
372
|
try:
|
|
315
|
-
data_buf = afc.get_file_contents(
|
|
373
|
+
data_buf = afc.get_file_contents("/Books/iBooksData2.plist")
|
|
316
374
|
except AfcFileNotFoundError:
|
|
317
375
|
pass
|
|
318
376
|
else:
|
|
319
|
-
ret[
|
|
377
|
+
ret["iBooks Data 2"] = data_buf
|
|
320
378
|
if itunes_settings:
|
|
321
|
-
ret[
|
|
379
|
+
ret["iTunes Settings"] = itunes_settings
|
|
322
380
|
return ret
|
|
323
381
|
|
|
324
382
|
@contextmanager
|
|
325
383
|
def _backup_lock(self, afc, notification_proxy):
|
|
326
384
|
notification_proxy.notify_post(NP_SYNC_WILL_START)
|
|
327
|
-
lockfile = afc.fopen(
|
|
385
|
+
lockfile = afc.fopen("/com.apple.itunes.lock_sync", "r+")
|
|
328
386
|
if lockfile:
|
|
329
387
|
notification_proxy.notify_post(NP_SYNC_LOCK_REQUEST)
|
|
330
388
|
for _ in range(50):
|
|
331
389
|
try:
|
|
332
390
|
afc.lock(lockfile, AFC_LOCK_EX)
|
|
333
391
|
except AfcException as e:
|
|
334
|
-
if e.status ==
|
|
392
|
+
if e.status == AfcError.OP_WOULD_BLOCK:
|
|
335
393
|
time.sleep(0.2)
|
|
336
394
|
else:
|
|
337
395
|
afc.fclose(lockfile)
|
|
@@ -341,7 +399,7 @@ class Mobilebackup2Service(LockdownService):
|
|
|
341
399
|
break
|
|
342
400
|
else: # No break, lock failed.
|
|
343
401
|
afc.fclose(lockfile)
|
|
344
|
-
raise PyMobileDevice3Exception(
|
|
402
|
+
raise PyMobileDevice3Exception("Failed to lock itunes sync file")
|
|
345
403
|
try:
|
|
346
404
|
yield
|
|
347
405
|
finally:
|
|
@@ -352,9 +410,9 @@ class Mobilebackup2Service(LockdownService):
|
|
|
352
410
|
@staticmethod
|
|
353
411
|
def _assert_backup_exists(backup_directory: Path, identifier: str):
|
|
354
412
|
device_directory = backup_directory / identifier
|
|
355
|
-
assert (device_directory /
|
|
356
|
-
assert (device_directory /
|
|
357
|
-
assert (device_directory /
|
|
413
|
+
assert (device_directory / "Info.plist").exists()
|
|
414
|
+
assert (device_directory / "Manifest.plist").exists()
|
|
415
|
+
assert (device_directory / "Status.plist").exists()
|
|
358
416
|
|
|
359
417
|
@contextmanager
|
|
360
418
|
def device_link(self, backup_directory):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import socket
|
|
2
2
|
from collections.abc import Generator
|
|
3
|
-
from typing import Union
|
|
3
|
+
from typing import Optional, Union
|
|
4
4
|
|
|
5
5
|
from pymobiledevice3.exceptions import NotificationTimeoutError
|
|
6
6
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
@@ -9,13 +9,13 @@ from pymobiledevice3.services.lockdown_service import LockdownService
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class NotificationProxyService(LockdownService):
|
|
12
|
-
SERVICE_NAME =
|
|
13
|
-
RSD_SERVICE_NAME =
|
|
12
|
+
SERVICE_NAME = "com.apple.mobile.notification_proxy"
|
|
13
|
+
RSD_SERVICE_NAME = "com.apple.mobile.notification_proxy.shim.remote"
|
|
14
14
|
|
|
15
|
-
INSECURE_SERVICE_NAME =
|
|
16
|
-
RSD_INSECURE_SERVICE_NAME =
|
|
15
|
+
INSECURE_SERVICE_NAME = "com.apple.mobile.insecure_notification_proxy"
|
|
16
|
+
RSD_INSECURE_SERVICE_NAME = "com.apple.mobile.insecure_notification_proxy.shim.remote"
|
|
17
17
|
|
|
18
|
-
def __init__(self, lockdown: LockdownServiceProvider, insecure=False, timeout: Union[float, int] = None):
|
|
18
|
+
def __init__(self, lockdown: LockdownServiceProvider, insecure=False, timeout: Optional[Union[float, int]] = None):
|
|
19
19
|
if isinstance(lockdown, RemoteServiceDiscoveryService):
|
|
20
20
|
secure_service_name = self.RSD_SERVICE_NAME
|
|
21
21
|
insecure_service_name = self.RSD_INSECURE_SERVICE_NAME
|
|
@@ -32,13 +32,13 @@ class NotificationProxyService(LockdownService):
|
|
|
32
32
|
self.service.socket.settimeout(timeout)
|
|
33
33
|
|
|
34
34
|
def notify_post(self, name: str) -> None:
|
|
35
|
-
"""
|
|
36
|
-
self.service.send_plist({
|
|
35
|
+
"""Send notification to the device's notification_proxy."""
|
|
36
|
+
self.service.send_plist({"Command": "PostNotification", "Name": name})
|
|
37
37
|
|
|
38
38
|
def notify_register_dispatch(self, name: str) -> None:
|
|
39
|
-
"""
|
|
40
|
-
self.logger.info(f
|
|
41
|
-
self.service.send_plist({
|
|
39
|
+
"""Tells the device to send a notification on the specified event."""
|
|
40
|
+
self.logger.info(f"Observing {name}")
|
|
41
|
+
self.service.send_plist({"Command": "ObserveNotification", "Name": name})
|
|
42
42
|
|
|
43
43
|
def receive_notification(self) -> Generator[dict, None, None]:
|
|
44
44
|
while True:
|