Files
vphone-cli/research/devmode_xpc_protocol.md
2026-03-04 13:34:02 +08:00

122 lines
4.0 KiB
Markdown

# Developer Mode via AMFI XPC
How iOS developer mode is enabled programmatically, based on TrollStore's implementation.
## XPC Service
**Mach service:** `com.apple.amfi.xpc`
AMFI (Apple Mobile File Integrity) daemon exposes an XPC endpoint for developer mode control on iOS 16+.
## Required Entitlement
```
com.apple.private.amfi.developer-mode-control = true
```
Without this entitlement the XPC connection to amfid is rejected.
## Message Protocol
Messages are serialized using private CoreFoundation-XPC bridge functions:
```objc
extern xpc_object_t _CFXPCCreateXPCMessageWithCFObject(CFTypeRef obj);
extern CFTypeRef _CFXPCCreateCFObjectFromXPCMessage(xpc_object_t obj);
```
### Request
NSDictionary with a single key:
```objc
@{@"action": @(action)}
```
### Actions
| Action | Value | Behavior |
| -------------------- | ----- | ---------------------------------------------------------------------------- |
| `kAMFIActionArm` | 0 | Arm developer mode — takes effect on next reboot, user must select "Turn On" |
| `kAMFIActionDisable` | 1 | Disable developer mode immediately |
| `kAMFIActionStatus` | 2 | Query current state |
### Response
XPC reply dict contains a `"cfreply"` key holding the CF-serialized response:
```objc
xpc_object_t cfReply = xpc_dictionary_get_value(reply, "cfreply");
NSDictionary *dict = _CFXPCCreateCFObjectFromXPCMessage(cfReply);
```
Response fields:
| Key | Type | Description |
| --------- | -------- | ------------------------------------------------ |
| `success` | BOOL | Whether the XPC call succeeded |
| `status` | BOOL | Current developer mode state (for Status action) |
| `armed` | BOOL | Whether armed for reboot (for Arm action) |
| `error` | NSString | Error description if success is false |
## Arming Flow
1. Query status (`kAMFIActionStatus`)
2. If already enabled, done
3. Send arm (`kAMFIActionArm`)
4. Device must reboot; user selects "Turn On" in the prompt
5. Developer mode is now active
Arming does **not** enable developer mode immediately. It sets a flag that triggers the enable prompt on the next reboot. Disabling (`kAMFIActionDisable`) takes effect immediately.
## TrollStore Reference
Source: `references/TrollStore/RootHelper/devmode.m`
TrollStore separates privileges: the main app has no AMFI entitlement; all privileged operations go through RootHelper which has `com.apple.private.amfi.developer-mode-control`.
Key functions:
- `checkDeveloperMode()` — returns current state, YES on iOS <16 (devmode doesn't exist)
- `armDeveloperMode(BOOL *alreadyEnabled)` check + arm in one call
- `startConnection()` creates and resumes XPC connection to `com.apple.amfi.xpc`
- `sendXPCRequest()` CF dict XPC message sync reply CF dict
## vphoned Implementation
Added as `devmode` capability in vphoned guest agent:
### Protocol Messages
**Status query:**
```json
{"t": "devmode", "action": "status"}
{"t": "ok", "enabled": true}
```
**Enable (arm):**
```json
{"t": "devmode", "action": "enable"}
{"t": "ok", "already_enabled": false, "msg": "developer mode armed, reboot to activate"}
```
### Entitlements
Added to `scripts/vphoned/entitlements.plist`:
```xml
<key>com.apple.private.amfi.developer-mode-control</key>
<true/>
```
### Host-Side API (VPhoneControl.swift)
```swift
let status = try await control.sendDevModeStatus() // -> DevModeStatus (enabled: Bool)
let result = try await control.sendDevModeEnable() // -> DevModeEnableResult (alreadyEnabled: Bool, message: String)
```
Both methods use `sendRequest()` with pending request tracking the response is returned via `async throws`, not logged to console. Callers (e.g. `VPhoneMenuConnect`) display results as `NSAlert` sheets on the VM window.