Files
iDescriptor/src/rust/src/service_manager.rs
T

1225 lines
45 KiB
Rust

use crate::{APP_DEVICE_STATE, RUNTIME, run_sync, utils};
use cxx_qt::Threading;
use cxx_qt_lib::{QByteArray, QList, QMap, QMapPair_QString_QVariant, QString, QVariant};
use idevice::afc::opcode::AfcFopenMode;
use idevice::services::core_device_proxy::CoreDeviceProxy;
use idevice::{
IdeviceService, RsdService, amfi,
dvt::{location_simulation::LocationSimulationClient, remote_server::RemoteServerClient},
installation_proxy::InstallationProxyClient,
mobile_image_mounter::ImageMounter,
provider::IdeviceProvider,
rsd::RsdHandshake,
simulate_location::LocationSimulationService,
springboardservices::SpringBoardServicesClient,
};
use plist::Value;
use plist_macro::plist;
use serde_json;
use std::{io::Read, pin::Pin, time::Duration};
#[cxx_qt::bridge(namespace = "CXX")]
mod qobject {
#[namespace = ""]
unsafe extern "C++" {
include!("cxx-qt-lib/qstring.h");
include!("cxx-qt-lib/qlist.h");
include!("cxx-qt-lib/qbytearray.h");
include!("cxx-qt-lib/qmap.h");
type QString = cxx_qt_lib::QString;
type QList_QString = cxx_qt_lib::QList<QString>;
type QByteArray = cxx_qt_lib::QByteArray;
type QMap_QString_QVariant = cxx_qt_lib::QMap<cxx_qt_lib::QMapPair_QString_QVariant>;
}
extern "RustQt" {
#[qobject]
type ServiceManager = super::RServiceManager;
#[qinvokable]
fn set_udid(self: Pin<&mut ServiceManager>, udid: &QString);
#[qinvokable]
fn get_cable_info(&self);
#[qinvokable]
fn reveal_developer_mode_option_in_ui(&self);
#[qinvokable]
fn query_mobilegestalt(&self, keys: &QList_QString);
#[qinvokable]
fn mount_dev_image(&self, image_path: &QString, sig: &QString);
#[qinvokable]
fn get_mounted_image(&self);
#[qinvokable]
fn fetch_installed_apps(&self);
#[qinvokable]
fn set_location(&self, latitude: &QString, longitude: &QString) -> i32;
#[qinvokable]
fn fetch_app_icon(&self, bundle_id: QString);
#[qsignal]
fn cable_info_retrieved(self: Pin<&mut ServiceManager>, info: QString);
#[qsignal]
fn mobilegestalt_info_retrieved(
self: Pin<&mut ServiceManager>,
info: QMap_QString_QVariant,
);
#[qsignal]
fn dev_image_mounted(self: Pin<&mut ServiceManager>, success: bool, is_locked: bool);
#[qsignal]
fn developer_mode_option_revealed(self: Pin<&mut ServiceManager>, success: bool);
#[qsignal]
fn mounted_image_retrieved(
self: Pin<&mut ServiceManager>,
success: bool,
is_locked: bool,
sig: QByteArray,
sig_length: u64,
);
#[qsignal]
fn installed_apps_retrieved(self: Pin<&mut ServiceManager>, apps: &QMap_QString_QVariant);
#[qsignal]
fn app_icon_loaded(
self: Pin<&mut ServiceManager>,
bundle_id: QString,
icon_data: QByteArray,
);
#[qsignal]
fn battery_info_updated(self: Pin<&mut ServiceManager>, info: &QString);
#[qinvokable]
fn fetch_disk_usage(&self, skip_gallery_usage: bool);
#[qsignal]
fn disk_usage_retrieved(
self: Pin<&mut ServiceManager>,
success: bool,
apps_usage: u64,
gallery_usage: u64,
);
#[qinvokable]
fn restart(&self) -> bool;
#[qinvokable]
fn shutdown(&self) -> bool;
#[qinvokable]
fn enter_recovery_mode(&self) -> bool;
#[qinvokable]
fn install_ipa(&self, ipa_path: &QString);
#[qsignal]
fn install_ipa_init(self: Pin<&mut ServiceManager>, started: bool, state: QString);
#[qsignal]
fn install_ipa_progress(self: Pin<&mut ServiceManager>, progress: f64, state: QString);
#[qinvokable]
fn enable_wifi_connections(&self);
#[qsignal]
fn enable_wifi_connections_result(self: Pin<&mut ServiceManager>, success: bool);
}
impl cxx_qt::Threading for ServiceManager {}
impl cxx_qt::Constructor<(QString, u32), NewArguments = (QString, u32)> for ServiceManager {}
}
#[derive(Default)]
pub struct RServiceManager {
udid: QString,
ios_version: u32,
}
impl cxx_qt::Constructor<(QString, u32)> for qobject::ServiceManager {
type BaseArguments = ();
type InitializeArguments = ();
type NewArguments = (QString, u32);
fn route_arguments(
args: (QString, u32),
) -> (
Self::NewArguments,
Self::BaseArguments,
Self::InitializeArguments,
) {
(args, (), ())
}
fn new(args: (QString, u32)) -> RServiceManager {
RServiceManager {
udid: args.0,
ios_version: args.1,
}
}
fn initialize(self: Pin<&mut Self>, _arguments: Self::InitializeArguments) {
let udid_for_log = self.get_udid().to_string();
println!("ServiceManager::initialize called for UDID: {udid_for_log}");
self.start_update_battery_info_interval();
}
}
impl qobject::ServiceManager {
pub fn start_update_battery_info_interval(self: Pin<&mut Self>) {
let qt_thread = self.qt_thread();
let udid = self.get_udid().to_string();
println!("Starting battery info update interval for device {udid}");
RUNTIME.spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let device = match maybe_device {
Some(d) => d,
None => {
println!("Battery info interval: Device {udid} not found");
return;
}
};
println!("Battery info interval: Fetching battery info for device {udid}");
utils::get_battery_info(&mut *device.diag.lock().await)
.await
.map(|info| {
let mut buf = Vec::new();
if Value::Dictionary(info).to_writer_xml(&mut buf).is_ok() {
if let Ok(s) = String::from_utf8(buf) {
qt_thread
.queue(move |t| {
t.battery_info_updated(&QString::from(s));
})
.ok();
}
}
});
}
});
}
fn get_udid(&self) -> &QString {
use cxx_qt::CxxQtType;
&self.rust().udid
}
fn get_ios_version(&self) -> u32 {
use cxx_qt::CxxQtType;
self.rust().ios_version
}
fn set_udid(mut self: Pin<&mut Self>, udid: &QString) {
use cxx_qt::CxxQtType;
self.as_mut().rust_mut().udid = udid.clone();
}
fn query_mobilegestalt(&self, keys: &QList<QString>) {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
let keys_owned = keys.clone();
RUNTIME.spawn(async move {
let mut map = QMap::<QMapPair_QString_QVariant>::default();
let keys_vec: Vec<String> = keys_owned
.into_iter()
.map(|qstr| qstr.to_string())
.collect();
let qt_thread = qt_t.clone();
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("query_mobilegestalt: device {udid} not found");
let _ = qt_thread.queue(move |t| {
t.mobilegestalt_info_retrieved(map);
});
return;
}
};
let result = device.diag.lock().await.mobilegestalt(Some(keys_vec)).await;
match result {
Ok(opt_dict) => {
if let Some(mut root_dict) = opt_dict {
let mobilegestalt_value = root_dict.remove("MobileGestalt");
let inner_mobilegestalt_dict = match mobilegestalt_value {
Some(Value::Dictionary(dict)) => dict,
_ => {
eprintln!(
"query_mobilegestalt: MobileGestalt key not found or not a dictionary for device {udid}."
);
let _ = qt_thread.queue(move |t| {
t.mobilegestalt_info_retrieved(map);
});
return;
}
};
for (k, v) in inner_mobilegestalt_dict.into_iter() {
let v_str = format!("{v:?}");
map.insert(QString::from(k), QVariant::from(&QString::from(v_str)));
}
}
let _ = qt_thread.queue(move |t| {
t.mobilegestalt_info_retrieved(map);
});
}
Err(e) => {
eprintln!(
"query_mobilegestalt: error querying MobileGestalt for device {udid}: {e}"
);
let _ = qt_thread.queue(move |t| {
t.mobilegestalt_info_retrieved(map);
});
}
}
});
}
fn get_cable_info(&self) {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
RUNTIME.spawn(async move {
let qt_thread = qt_t.clone();
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("get_cable_info: device {udid} not found");
let _ = qt_thread.queue(|t| {
t.cable_info_retrieved(QString::from(""));
});
return;
}
};
let res = utils::get_cable_info(&mut *device.diag.lock().await).await;
match res {
Some(dict) => {
let mut buf = Vec::new();
if Value::Dictionary(dict).to_writer_xml(&mut buf).is_err() {
eprintln!(
"get_cable_info: Failed to serialize ioregistry values to XML for device {udid}."
);
let _ = qt_thread.queue(|t| {
t.cable_info_retrieved(QString::from(""));
});
return;
}
match String::from_utf8(buf) {
Ok(s) => {
let _ = qt_thread.queue(move |t| {
t.cable_info_retrieved(QString::from(s));
});
}
Err(_) => {
eprintln!(
"get_cable_info: Failed to convert ioregistry XML data to string for device {udid}."
);
let _ = qt_thread.queue(|t| {
t.cable_info_retrieved(QString::from(""));
});
}
}
}
None => {
eprintln!("get_cable_info: Failed to get ioregistry for device {udid}");
let _ = qt_thread.queue(|t| {
t.cable_info_retrieved(QString::from(""));
});
}
}
});
}
fn mount_dev_image(&self, image_path: &QString, sig: &QString) {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
let image = image_path.to_string();
let signature = sig.to_string();
RUNTIME.spawn(async move {
let qt_thread = qt_t.clone();
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("get_cable_info: device {udid} not found");
let _ = qt_thread.queue(|t| {
t.dev_image_mounted(false,false);
});
return;
}
};
let mut mounter = match {
let provider_guard = device.provider.lock().await;
let provider_ref: &dyn IdeviceProvider = provider_guard.as_ref();
ImageMounter::connect(provider_ref).await
} {
Ok(m) => m,
Err(e) => {
eprintln!("mount_dev_image: Failed to connect to ImageMounter for device {udid}: {e}");
let _ = qt_thread.queue(|t| {
t.dev_image_mounted(false,false);
});
return;
}
};
let mut file = match std::fs::File::open(&image) {
Ok(f) => f,
Err(e) => {
eprintln!("mount_dev_image: Failed to open image file {image} for device {udid}: {e}");
let _ = qt_thread.queue(|t| {
t.dev_image_mounted(false,false);
});
return;
}
};
let mut buf = Vec::new();
if let Err(e) = file.read_to_end(&mut buf) {
eprintln!("mount_dev_image: Failed to read image file {image} for device {udid}: {e}");
let _ = qt_thread.queue(|t| {
t.dev_image_mounted(false,false);
});
return;
}
let mut sig_file = match std::fs::File::open(&signature) {
Ok(f) => f,
Err(e) => {
eprintln!("mount_dev_image: Failed to open signature file {signature} for device {udid}: {e}");
let _ = qt_thread.queue(|t| {
t.dev_image_mounted(false,false);
});
return;
}
};
let mut sig_buf: Vec<u8> = Vec::new();
if let Err(e) = sig_file.read_to_end(&mut sig_buf) {
eprintln!("mount_dev_image: Failed to read signature file {signature} for device {udid}: {e}");
let _ = qt_thread.queue(|t| {
t.dev_image_mounted(false,false);
});
return;
}
match mounter.mount_developer(&buf, sig_buf).await {
Ok(_) => {
let _ = qt_thread.queue(|t| {
t.dev_image_mounted(true ,false);
});
}
Err(idevice::IdeviceError::DeviceLocked) => {
eprintln!("mount_dev_image: Failed to mount developer image for device {udid}: device locked");
let _ = qt_thread.queue(|t| {
t.dev_image_mounted(false,true);
}).ok();
}
Err(e) => {
eprintln!("mount_dev_image: Failed to mount developer image for device {udid}: {e}");
let _ = qt_thread.queue(|t| {
t.dev_image_mounted(false,false);
}).ok();
}
};
});
}
fn get_mounted_image(&self) {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
RUNTIME.spawn(async move {
let qt_thread = qt_t.clone();
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("get_mounted_image: device {udid} not found");
let _ = qt_thread.queue(|t| {
t.mounted_image_retrieved(false,false,QByteArray::default(), 0);
}).ok();
return;
}
};
let mut mounter = match {
let provider_guard = device.provider.lock().await;
let provider_ref: &dyn IdeviceProvider = provider_guard.as_ref();
ImageMounter::connect(provider_ref).await
} {
Ok(m) => m,
Err(e) => {
eprintln!("get_mounted_image: Failed to connect to ImageMounter for device {udid}: {e}");
let _ = qt_thread.queue(|t| {
t.mounted_image_retrieved(false,false,QByteArray::default(), 0);
}).ok();
return;
}
};
match mounter.lookup_image("Developer").await {
Ok(res) => {
let _ = qt_thread.queue(move|t| {
t.mounted_image_retrieved(true,false,QByteArray::from(&res[..]), res.len() as u64);
}).ok();
}
Err(idevice::IdeviceError::DeviceLocked) => {
eprintln!("get_mounted_image: Failed to lookup mounted developer image for device {udid}: device locked");
let _ = qt_thread.queue(|t| {
t.mounted_image_retrieved(false,true,QByteArray::default(), 0);
}).ok();
}
Err(idevice::IdeviceError::NotFound) => {
eprintln!("get_mounted_image: No mounted developer image found for device {udid}");
let _ = qt_thread.queue(|t| {
t.mounted_image_retrieved(true,false,QByteArray::default(), 0);
}).ok();
}
Err(e) => {
eprintln!("get_mounted_image: Failed to lookup mounted developer image for device {udid}: {e}");
let _ = qt_thread.queue(|t| {
t.mounted_image_retrieved(false,false,QByteArray::default(), 0);
}).ok();
}
};
});
}
fn reveal_developer_mode_option_in_ui(&self) {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
RUNTIME.spawn(async move {
let qt_thread = qt_t.clone();
let mut amfi_client = match {
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("get_cable_info: device {udid} not found");
let _ = qt_thread.queue(|t| {
t.developer_mode_option_revealed(false);
}).ok();
return;
}
};
let provider_guard = device.provider.lock().await;
let provider_ref: &dyn IdeviceProvider = provider_guard.as_ref();
amfi::AmfiClient::connect(provider_ref).await
} {
Ok(c) => c,
Err(e) => {
eprintln!("reveal_developer_mode_option_in_ui: Failed to connect to AMFI service for device {udid}: {e}");
let _ = qt_thread.queue(|t| {
t.developer_mode_option_revealed(false);
}).ok();
return;
}
};
match amfi_client.reveal_developer_mode_option_in_ui().await {
Ok(_) => {
let _ = qt_thread.queue(|t| {
t.developer_mode_option_revealed(true);
}).ok();
}
Err(e) => {
eprintln!("reveal_developer_mode_option_in_ui: Failed to reveal developer mode option in UI for device {udid}: {e}");
let _ = qt_thread.queue(|t| {
t.developer_mode_option_revealed(false);
}).ok();
}
}
});
}
fn fetch_installed_apps(&self) {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
RUNTIME.spawn(async move {
let qt_thread = qt_t.clone();
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let mut all_apps = QMap::<QMapPair_QString_QVariant>::default();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("fetch_installed_apps: device {udid} not found");
let _ = qt_thread.queue(move |t| {
t.installed_apps_retrieved(&all_apps);
});
return;
}
};
let mut ins_client = match {
let provider_guard = device.provider.lock().await;
let provider_ref: &dyn IdeviceProvider = provider_guard.as_ref();
InstallationProxyClient::connect(provider_ref).await
} {
Ok(c) => c,
Err(e) => {
eprintln!("fetch_installed_apps: Failed to connect to InstallationProxy service for device {udid}: {e}");
let _ = qt_thread.queue( move |t| {
t.installed_apps_retrieved(&all_apps);
});
return;
}
};
// Get both User and System apps
for app_type in ["User", "System"] {
let client_options = plist!({
"ApplicationType": app_type,
"ReturnAttributes": [
"CFBundleIdentifier",
"CFBundleDisplayName",
"CFBundleShortVersionString",
"CFBundleVersion",
"UIFileSharingEnabled"
]
});
let apps = match ins_client.browse(Some(client_options)).await {
Ok(apps) => apps,
Err(e) => {
eprintln!("fetch_installed_apps: Failed to browse installed apps for device {udid} and app type {app_type}: {e}");
continue; // Skip this app type
}
};
for app_info in apps {
if let plist::Value::Dictionary(app_dict) = app_info {
let bundle_id = app_dict
.get("CFBundleIdentifier")
.and_then(|v| v.as_string())
.unwrap_or_default();
if bundle_id.is_empty() {
continue;
}
let display = app_dict
.get("CFBundleDisplayName")
.and_then(|v| v.as_string())
.unwrap_or_default();
let version = app_dict
.get("CFBundleShortVersionString")
.and_then(|v| v.as_string())
.unwrap_or_default();
let fs_enabled = app_dict
.get("UIFileSharingEnabled")
.and_then(|v| v.as_boolean())
.unwrap_or(false);
let app_json = format!(
"{{\"bundle_id\":{},\"CFBundleDisplayName\":{},\"CFBundleShortVersionString\":{},\"UIFileSharingEnabled\":{},\"app_type\":{}}}",
serde_json::to_string(&bundle_id).unwrap_or_else(|_| format!("\"{}\"", bundle_id)),
serde_json::to_string(&display).unwrap_or_else(|_| format!("\"{}\"", display)),
serde_json::to_string(&version).unwrap_or_else(|_| format!("\"{}\"", version)),
fs_enabled,
serde_json::to_string(&app_type).unwrap_or_else(|_| format!("\"{}\"", app_type)),
);
all_apps.insert(
QString::from(bundle_id),
QVariant::from(&QString::from(app_json)),
);
}
}
}
let _ = qt_thread.queue(move |t| {
t.installed_apps_retrieved(&all_apps);
});
});
}
fn fetch_app_icon(&self, bundle_id: QString) {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
let bundle_id_owned = bundle_id.clone();
RUNTIME.spawn(async move {
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("fetch_app_icon: device {udid} not found");
return;
}
};
let mut springboard_client = match {
let provider_guard = device.provider.lock().await;
let provider_ref: &dyn IdeviceProvider = provider_guard.as_ref();
SpringBoardServicesClient::connect(provider_ref).await
} {
Ok(c) => c,
Err(e) => {
eprintln!("fetch_app_icon: Failed to connect to InstallationProxy service for device {udid}: {e}");
return;
}
};
match springboard_client.get_icon_pngdata(bundle_id_owned.to_string()).await {
Ok(icon_data) => {
qt_t.queue(move |t| {
t.app_icon_loaded(bundle_id_owned, QByteArray::from(&icon_data[..]));
}).ok();
}
Err(e) => {
eprintln!("fetch_app_icon: Failed to fetch app icon for bundle ID {} on device {udid}: {e}", bundle_id_owned);
}
};
});
}
fn set_location(&self, latitude: &QString, longitude: &QString) -> i32 {
let udid = self.get_udid().to_string();
let ios_version = self.get_ios_version();
/*
FIXME: use RUNTIME.spawn in the future
*/
RUNTIME.block_on(async move {
tokio::select! {
res = async {
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("set_location: device {udid} not found");
return -1;
}
};
let result = {
let provider_guard = device.provider.lock().await;
if ios_version < 17 {
set_device_location_lockdown( provider_guard.as_ref(), latitude.to_string().as_str(), longitude.to_string().as_str()).await
} else {
println!("Using RSD path for setting location on device {udid} with iOS version {ios_version}");
let proxy_res = {
CoreDeviceProxy::connect(provider_guard.as_ref()).await
};
match proxy_res {
Ok(proxy) => set_device_location_rsd(proxy, utils::qstring_to_f64(latitude).unwrap_or(0.0), utils::qstring_to_f64(longitude).unwrap_or(0.0)).await,
Err(err) => {
println!("Failed to connect to CoreDeviceProxy for device {udid}, cannot set location using RSD path.");
return err.code();
},
}
}
};
match result {
Ok(_) => 0,
Err(e) => {
eprintln!(
"set_location: failed to set virtual location for device {udid}: {e:?}"
);
return e.code();
}
}
} => {
res
}
_ = tokio::time::sleep(Duration::from_secs(10)) => {
eprintln!("set_location: timed out");
idevice::IdeviceError::Timeout.code()
}
}
})
}
fn fetch_disk_usage(&self, skip_gallery_usage: bool) {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
RUNTIME.spawn(async move {
let qt_thread = qt_t.clone();
let mut instproxy = {
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("fetch_disk_usage: device {udid} not found");
return;
}
};
match InstallationProxyClient::connect(device.provider.lock().await.as_ref()).await {
Ok(c) => c,
Err(e) => {
eprintln!("fetch_disk_usage: Failed to connect to InstallationProxy service for device {udid}: {e}");
qt_thread.queue(move |t| {
t.disk_usage_retrieved(false, 0, 0);
}).ok();
return;
}
}
};
match utils::calculate_apps_usage(&mut instproxy).await {
Ok(apps_usage) => {
if skip_gallery_usage {
qt_thread.queue(move |t| {
t.disk_usage_retrieved(true, apps_usage, 0);
}).ok();
return;
}
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("fetch_disk_usage: device {udid} not found");
return;
}
};
let mut afc = device.afc.lock().await;
// FIXME: go safer here
let mut fd = afc.open("/PhotoData/Photos.sqlite", AfcFopenMode::RdOnly).await.map_err(|e| {
eprintln!("fetch_disk_usage: Failed to read gallery database for device {udid}: {e}");
e
}).unwrap();
let mut gallery_db_bytes = fd.read_entire().await.unwrap();
match utils::query_gallery_usage(&mut gallery_db_bytes) {
Ok(gallery_usage) => {
qt_thread.queue(move |t| {
t.disk_usage_retrieved(true, apps_usage, gallery_usage);
}).ok();
}
Err(e) => {
eprintln!("fetch_disk_usage: Failed to calculate gallery disk usage for device {udid}: {e}");
qt_thread.queue(move |t| {
t.disk_usage_retrieved(true, apps_usage, 0);
}).ok();
}
}
}
Err(e) => {
eprintln!("fetch_disk_usage: Failed to calculate apps disk usage for device {udid}: {e}");
qt_thread.queue(move |t| {
t.disk_usage_retrieved(false, 0, 0);
}).ok();
}
};
});
}
fn restart(&self) -> bool {
let udid = self.get_udid().to_string();
run_sync(async move {
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("restart: device {udid} not found");
return false;
}
};
if let Err(e) = device.diag.lock().await.restart().await {
eprintln!("restart: Failed to restart device {udid}: {e}");
return false;
}
return true;
})
}
fn shutdown(&self) -> bool {
let udid = self.get_udid().to_string();
run_sync(async move {
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("shutdown: device {udid} not found");
return false;
}
};
if let Err(e) = device.diag.lock().await.shutdown().await {
eprintln!("shutdown: Failed to shutdown device {udid}: {e}");
return false;
}
return true;
})
}
fn enter_recovery_mode(&self) -> bool {
let udid = self.get_udid().to_string();
run_sync(async move {
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("enter_recovery_mode: device {udid} not found");
return false;
}
};
if let Err(e) = device.lockdown.lock().await.enter_recovery().await {
eprintln!(
"enter_recovery_mode: Failed to enter recovery mode for device {udid}: {e}"
);
return false;
}
return true;
})
}
fn install_ipa(&self, local_ipa_path: &QString) {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
let local_ipa_path_owned = local_ipa_path.clone().to_string();
// FIXME: this is a bit hacky
let ipa_path_on_device = format!(
"/PublicStaging/{}",
local_ipa_path
.to_string()
.split('/')
.last()
.unwrap_or("app.ipa")
);
RUNTIME.spawn(async move {
let qt_thread = qt_t.clone();
let mut ins_client = match {
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("install_ipa: device {udid} not found");
qt_thread.queue(move |t| {
t.install_ipa_init(false, QString::from("Device not found"));
}).ok();
return;
}
};
let mut afc = device.afc.lock().await;
// Create the staging directory
match utils::ensure_public_staging(&mut afc).await {
Ok(_) => (),
Err(e) => {
eprintln!("install_ipa: Failed to ensure /PublicStaging directory exists on device {udid}: {e}");
qt_thread.queue(move |t| {
t.install_ipa_init(false, QString::from("Failed to prepare device for IPA upload"));
}).ok();
return;
}
};
match std::fs::exists(&local_ipa_path_owned) {
Ok(true) => (),
Ok(false) => {
eprintln!("install_ipa: IPA file not found at path {local_ipa_path_owned}");
qt_thread.queue(move |t| {
t.install_ipa_init(false, QString::from("IPA file not found"));
}).ok();
return;
}
Err(e) => {
eprintln!("install_ipa: Failed to check if IPA file exists at path {local_ipa_path_owned}: {e}");
qt_thread.queue(move |t| {
t.install_ipa_init(false, QString::from("Failed to access IPA file"));
}).ok();
return;
}
}
match afc.open(&ipa_path_on_device, AfcFopenMode::WrOnly).await {
Ok(mut fd) => {
let mut local_file = match std::fs::File::open(&local_ipa_path_owned) {
Ok(f) => f,
Err(e) => {
eprintln!("install_ipa: Failed to open local IPA file for device {udid}: {e}");
qt_thread.queue(move |t| {
t.install_ipa_init(false, QString::from("Failed to open local IPA file"));
}).ok();
return;
}
};
let mut file_btytes = Vec::new();
match local_file.read_to_end(&mut file_btytes) {
Ok(bytes) => bytes,
Err(e) => {
eprintln!("install_ipa: Failed to read local IPA file for device {udid}: {e}");
qt_thread.queue(move |t| {
t.install_ipa_init(false, QString::from("Failed to read local IPA file"));
}).ok();
return;
}
};
if let Err(e) = fd.write_entire(&file_btytes).await {
eprintln!("install_ipa: Failed to upload IPA to device {udid}: {e}");
qt_thread.queue(move |t| {
t.install_ipa_init(false, QString::from("Failed to upload IPA to device"));
}).ok();
return;
}
}
Err(e) => {
eprintln!("install_ipa: Failed to create file on device {udid} for IPA upload: {e}");
qt_thread.queue(move |t| {
t.install_ipa_init(false, QString::from("Failed to create file on device for IPA upload"));
}).ok();
return;
}
}
let provider_guard = device.provider.lock().await;
let provider_ref: &dyn IdeviceProvider = provider_guard.as_ref();
InstallationProxyClient::connect(provider_ref).await
} {
Ok(c) => c,
Err(e) => {
eprintln!("install_ipa: Failed to connect to InstallationProxy service for device {udid}: {e}");
qt_thread.queue(move |t| {
t.install_ipa_init(false, QString::from("Failed to connect to Installation Proxy"));
}).ok();
return;
}
};
qt_thread.queue(move |t| {
t.install_ipa_init(true, QString::from("Connected to Installation Proxy"));
}).ok();
let state = String::from("Installing IPA");
let res = ins_client
.install_with_callback(
ipa_path_on_device,
None,
move |(percent, state)| {
let qt_thread = qt_thread.clone();
async move {
let progress = percent as f64 / 100.0;
qt_thread
.queue(move |t| {
t.install_ipa_progress(
progress,
QString::from(&state),
);
})
.ok();
println!(
"Installation progress: {percent}%"
);
}
},
state,
)
.await;
if let Err(e) = res {
eprintln!("install_ipa: Failed to install IPA on device {udid}: {e}");
} else {
println!("install_ipa: Successfully initiated installation on device {udid}");
}
});
}
fn enable_wifi_connections(&self) {
let qt_t = self.qt_thread();
let udid = self.get_udid().to_string();
RUNTIME.spawn(async move {
let qt_thread = qt_t.clone();
let lc_arc = {
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let device = match maybe_device {
Some(d) => d,
None => {
eprintln!("enable_wifi_connections: device {udid} not found");
let _ = qt_thread
.queue(|t| {
t.enable_wifi_connections_result(false);
})
.ok();
return;
}
};
device.lockdown.clone()
};
let mut lc = lc_arc.lock().await;
let value = Value::Boolean(true);
match lc
.set_value(
"EnableWifiConnections",
value,
Some("com.apple.mobile.wireless_lockdown"),
)
.await
{
Ok(_) => {
let _ = qt_thread
.queue(|t| {
t.enable_wifi_connections_result(true);
})
.ok();
}
Err(e) => {
eprintln!("wireless: LockdownClient::set_value failed: {e:?}");
let _ = qt_thread
.queue(|t| {
t.enable_wifi_connections_result(false);
})
.ok();
}
}
});
}
}
async fn set_device_location_lockdown(
provider: &dyn IdeviceProvider,
latitude: &str,
longitude: &str,
) -> Result<(), idevice::IdeviceError> {
let mut client = LocationSimulationService::connect(provider).await?;
client.set(latitude, longitude).await
}
// iOS 17+:
async fn set_device_location_rsd(
proxy: CoreDeviceProxy,
latitude: f64,
longitude: f64,
) -> Result<(), idevice::IdeviceError> {
let rsd_port = proxy.tunnel_info().server_rsd_port;
let adapter = proxy.create_software_tunnel()?;
let mut adapter = adapter.to_async_handle();
let stream = adapter.connect(rsd_port).await?;
let mut handshake = RsdHandshake::new(stream).await?;
let mut remote_server = RemoteServerClient::connect_rsd(&mut adapter, &mut handshake).await?;
remote_server.read_message(0).await?;
let mut location_client = LocationSimulationClient::new(&mut remote_server).await?;
location_client.set(latitude, longitude).await
}