mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
feat: add screenshotr for iOS versions below iOS17 (#27)
* feat: add screenshot for iOS versions below iOS17 * "cargo fmt" * Style cleanup --------- Co-authored-by: Jackson Coxson <jkcoxson@gmail.com>
This commit is contained in:
@@ -90,6 +90,7 @@ preboard_service = []
|
|||||||
obfuscate = ["dep:obfstr"]
|
obfuscate = ["dep:obfstr"]
|
||||||
restore_service = []
|
restore_service = []
|
||||||
rsd = ["xpc"]
|
rsd = ["xpc"]
|
||||||
|
screenshotr = []
|
||||||
syslog_relay = ["dep:bytes"]
|
syslog_relay = ["dep:bytes"]
|
||||||
tcp = ["tokio/net"]
|
tcp = ["tokio/net"]
|
||||||
tunnel_tcp_stack = [
|
tunnel_tcp_stack = [
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ pub mod preboard_service;
|
|||||||
pub mod restore_service;
|
pub mod restore_service;
|
||||||
#[cfg(feature = "rsd")]
|
#[cfg(feature = "rsd")]
|
||||||
pub mod rsd;
|
pub mod rsd;
|
||||||
|
#[cfg(feature = "screenshotr")]
|
||||||
|
pub mod screenshotr;
|
||||||
#[cfg(feature = "springboardservices")]
|
#[cfg(feature = "springboardservices")]
|
||||||
pub mod springboardservices;
|
pub mod springboardservices;
|
||||||
#[cfg(feature = "syslog_relay")]
|
#[cfg(feature = "syslog_relay")]
|
||||||
|
|||||||
123
idevice/src/services/screenshotr.rs
Normal file
123
idevice/src/services/screenshotr.rs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
//! iOS screenshotr service client
|
||||||
|
//!
|
||||||
|
//! Provides functionality for interacting with the screenshot service on iOS devices below iOS 17,
|
||||||
|
//! which allows taking screenshots.
|
||||||
|
|
||||||
|
use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
||||||
|
use log::{debug, warn};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
pub struct ScreenshotService {
|
||||||
|
/// Underlying device connection
|
||||||
|
pub idevice: Idevice,
|
||||||
|
}
|
||||||
|
impl IdeviceService for ScreenshotService {
|
||||||
|
fn service_name() -> Cow<'static, str> {
|
||||||
|
obf!("com.apple.mobile.screenshotr")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn from_stream(idevice: Idevice) -> Result<Self, IdeviceError> {
|
||||||
|
let mut client = Self::new(idevice);
|
||||||
|
// Perform DeviceLink handshake first
|
||||||
|
client.dl_version_exchange().await?;
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScreenshotService {
|
||||||
|
pub fn new(idevice: Idevice) -> Self {
|
||||||
|
Self { idevice }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn dl_version_exchange(&mut self) -> Result<(), IdeviceError> {
|
||||||
|
debug!("Starting DeviceLink version exchange");
|
||||||
|
// 1) Receive DLMessageVersionExchange
|
||||||
|
let (msg, _arr) = self.receive_dl_message().await?;
|
||||||
|
if msg != "DLMessageVersionExchange" {
|
||||||
|
warn!("Expected DLMessageVersionExchange, got {msg}");
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Send DLVersionsOk with version 400
|
||||||
|
let out = vec![
|
||||||
|
plist::Value::String("DLMessageVersionExchange".into()),
|
||||||
|
plist::Value::String("DLVersionsOk".into()),
|
||||||
|
plist::Value::Integer(400u64.into()),
|
||||||
|
];
|
||||||
|
self.send_dl_array(out).await?;
|
||||||
|
|
||||||
|
// 3) Receive DLMessageDeviceReady
|
||||||
|
let (msg2, _arr2) = self.receive_dl_message().await?;
|
||||||
|
if msg2 != "DLMessageDeviceReady" {
|
||||||
|
warn!("Expected DLMessageDeviceReady, got {msg2}");
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a raw DL array as binary plist
|
||||||
|
async fn send_dl_array(&mut self, array: Vec<plist::Value>) -> Result<(), IdeviceError> {
|
||||||
|
self.idevice.send_bplist(plist::Value::Array(array)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives any DL* message and returns (message_tag, full_array_value)
|
||||||
|
pub async fn receive_dl_message(&mut self) -> Result<(String, plist::Value), IdeviceError> {
|
||||||
|
if let Some(socket) = &mut self.idevice.socket {
|
||||||
|
let mut buf = [0u8; 4];
|
||||||
|
socket.read_exact(&mut buf).await?;
|
||||||
|
let len = u32::from_be_bytes(buf);
|
||||||
|
let mut body = vec![0; len as usize];
|
||||||
|
socket.read_exact(&mut body).await?;
|
||||||
|
let value: plist::Value = plist::from_bytes(&body)?;
|
||||||
|
if let plist::Value::Array(arr) = &value
|
||||||
|
&& let Some(plist::Value::String(tag)) = arr.first()
|
||||||
|
{
|
||||||
|
return Ok((tag.clone(), value));
|
||||||
|
}
|
||||||
|
warn!("Invalid DL message format");
|
||||||
|
Err(IdeviceError::UnexpectedResponse)
|
||||||
|
} else {
|
||||||
|
Err(IdeviceError::NoEstablishedConnection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn take_screenshot(&mut self) -> Result<Vec<u8>, IdeviceError> {
|
||||||
|
// Send DLMessageTakeScreenshot
|
||||||
|
|
||||||
|
let message_type_dict = crate::plist!(dict {
|
||||||
|
"MessageType": "ScreenShotRequest"
|
||||||
|
});
|
||||||
|
|
||||||
|
let out = vec![
|
||||||
|
plist::Value::String("DLMessageProcessMessage".into()),
|
||||||
|
plist::Value::Dictionary(message_type_dict),
|
||||||
|
];
|
||||||
|
self.send_dl_array(out).await?;
|
||||||
|
|
||||||
|
// Receive DLMessageScreenshotData
|
||||||
|
let (msg, value) = self.receive_dl_message().await?;
|
||||||
|
if msg != "DLMessageProcessMessage" {
|
||||||
|
warn!("Expected DLMessageProcessMessage, got {msg}");
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let plist::Value::Array(arr) = &value
|
||||||
|
&& arr.len() == 2
|
||||||
|
{
|
||||||
|
if let Some(plist::Value::Dictionary(dict)) = arr.get(1) {
|
||||||
|
if let Some(plist::Value::Data(data)) = dict.get("ScreenShotData") {
|
||||||
|
Ok(data.clone())
|
||||||
|
} else {
|
||||||
|
warn!("Invalid ScreenShotData format");
|
||||||
|
Err(IdeviceError::UnexpectedResponse)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Invalid DLMessageScreenshotData format");
|
||||||
|
Err(IdeviceError::UnexpectedResponse)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Invalid DLMessageScreenshotData format");
|
||||||
|
Err(IdeviceError::UnexpectedResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ use clap::{Arg, Command};
|
|||||||
use idevice::{IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake};
|
use idevice::{IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
|
use idevice::screenshotr::ScreenshotService;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -62,31 +64,43 @@ async fn main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let proxy = CoreDeviceProxy::connect(&*provider)
|
let mut res: Vec<u8> = Vec::new();
|
||||||
|
if let Ok(proxy) = CoreDeviceProxy::connect(&*provider).await {
|
||||||
|
let rsd_port = proxy.handshake.server_rsd_port;
|
||||||
|
|
||||||
|
let adapter = proxy.create_software_tunnel().expect("no software tunnel");
|
||||||
|
let mut adapter = adapter.to_async_handle();
|
||||||
|
let stream = adapter.connect(rsd_port).await.expect("no RSD connect");
|
||||||
|
|
||||||
|
// Make the connection to RemoteXPC
|
||||||
|
let mut handshake = RsdHandshake::new(stream).await.unwrap();
|
||||||
|
let mut ts_client = idevice::dvt::remote_server::RemoteServerClient::connect_rsd(
|
||||||
|
&mut adapter,
|
||||||
|
&mut handshake,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.expect("no core proxy");
|
.expect("Failed to connect");
|
||||||
let rsd_port = proxy.handshake.server_rsd_port;
|
ts_client.read_message(0).await.expect("no read??");
|
||||||
|
|
||||||
let adapter = proxy.create_software_tunnel().expect("no software tunnel");
|
let mut ts_client = idevice::dvt::screenshot::ScreenshotClient::new(&mut ts_client)
|
||||||
let mut adapter = adapter.to_async_handle();
|
|
||||||
let stream = adapter.connect(rsd_port).await.expect("no RSD connect");
|
|
||||||
|
|
||||||
// Make the connection to RemoteXPC
|
|
||||||
let mut handshake = RsdHandshake::new(stream).await.unwrap();
|
|
||||||
let mut ts_client =
|
|
||||||
idevice::dvt::remote_server::RemoteServerClient::connect_rsd(&mut adapter, &mut handshake)
|
|
||||||
.await
|
.await
|
||||||
.expect("Failed to connect");
|
.expect("Unable to get channel for take screenshot");
|
||||||
ts_client.read_message(0).await.expect("no read??");
|
res = ts_client
|
||||||
|
.take_screenshot()
|
||||||
let mut ts_client = idevice::dvt::screenshot::ScreenshotClient::new(&mut ts_client)
|
.await
|
||||||
.await
|
.expect("Failed to take screenshot");
|
||||||
.expect("Unable to get channel for take screenshot");
|
} else {
|
||||||
let res = ts_client
|
let mut screenshot_client = match ScreenshotService::connect(&*provider).await {
|
||||||
.take_screenshot()
|
Ok(client) => client,
|
||||||
.await
|
Err(e) => {
|
||||||
.expect("Failed to take screenshot");
|
eprintln!(
|
||||||
|
"Unable to connect to screenshotr service: {e} Ensure Developer Disk Image is mounted."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
res = screenshot_client.take_screenshot().await.unwrap();
|
||||||
|
}
|
||||||
match fs::write(output_path, &res) {
|
match fs::write(output_path, &res) {
|
||||||
Ok(_) => println!("Screenshot saved to: {}", output_path),
|
Ok(_) => println!("Screenshot saved to: {}", output_path),
|
||||||
Err(e) => eprintln!("Failed to write screenshot to file: {}", e),
|
Err(e) => eprintln!("Failed to write screenshot to file: {}", e),
|
||||||
|
|||||||
Reference in New Issue
Block a user