diff --git a/.gitignore b/.gitignore index def30f3..d7bccdf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ idevice.h /ffi/examples/build /.cache /ffi/examples/.cache +.vscode +build \ No newline at end of file diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index cd5b179..96f6752 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -15,6 +15,7 @@ pub mod provider; pub mod remote_server; pub mod remotexpc; pub mod usbmuxd; +pub mod sbservices; pub mod util; pub use errors::*; diff --git a/ffi/src/sbservices.rs b/ffi/src/sbservices.rs new file mode 100644 index 0000000..4ac8d7f --- /dev/null +++ b/ffi/src/sbservices.rs @@ -0,0 +1,171 @@ +use std::ffi::{c_void, CStr}; + +use idevice::{IdeviceError, IdeviceService, sbservices::SpringBoardServicesClient}; + +use crate::{ + IdeviceErrorCode, IdeviceHandle, RUNTIME, + provider::{TcpProviderHandle, UsbmuxdProviderHandle}, +}; + +pub struct SpringBoardServicesClientHandle(pub SpringBoardServicesClient); + +#[allow(non_camel_case_types)] +pub struct plist_t; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn springboard_services_proxy_connect_tcp( + provider: *mut TcpProviderHandle, + client: *mut *mut SpringBoardServicesClientHandle, +) -> IdeviceErrorCode { + if provider.is_null() || client.is_null() { + log::error!("Null pointer provided"); + return IdeviceErrorCode::InvalidArg; + } + + let res: Result = RUNTIME.block_on(async move { + // Take ownership of the provider (without immediately dropping it) + let provider_box = unsafe { Box::from_raw(provider) }; + + // Get a reference to the inner value + let provider_ref = &provider_box.0; + + // Connect using the reference + let result = SpringBoardServicesClient::connect(provider_ref).await; + + // Explicitly keep the provider_box alive until after connect completes + std::mem::forget(provider_box); + result + }); + + match res { + Ok(r) => { + let boxed = Box::new(SpringBoardServicesClientHandle(r)); + unsafe { *client = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => { + // If connection failed, the provider_box was already forgotten, + // so we need to reconstruct it to avoid leak + let _ = unsafe { Box::from_raw(provider) }; + e.into() + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn springboard_services_proxy_connect_usbmuxd( + provider: *mut UsbmuxdProviderHandle, + client: *mut *mut SpringBoardServicesClientHandle, +) -> IdeviceErrorCode { + if provider.is_null() { + log::error!("Provider is null"); + return IdeviceErrorCode::InvalidArg; + } + + let res: Result = RUNTIME.block_on(async move { + // Take ownership of the provider (without immediately dropping it) + let provider_box = unsafe { Box::from_raw(provider) }; + + // Get a reference to the inner value + let provider_ref = &provider_box.0; + + // Connect using the reference + let result = SpringBoardServicesClient::connect(provider_ref).await; + + // Explicitly keep the provider_box alive until after connect completes + std::mem::forget(provider_box); + result + }); + + match res { + Ok(r) => { + let boxed = Box::new(SpringBoardServicesClientHandle(r)); + unsafe { *client = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn springboard_services_proxy_new( + socket: *mut IdeviceHandle, + client: *mut *mut SpringBoardServicesClientHandle, +) -> IdeviceErrorCode { + if socket.is_null() { + return IdeviceErrorCode::InvalidArg; + } + let socket = unsafe { Box::from_raw(socket) }.0; + let r = SpringBoardServicesClient::new(socket); + let boxed = Box::new(SpringBoardServicesClientHandle(r)); + unsafe { *client = Box::into_raw(boxed) }; + IdeviceErrorCode::IdeviceSuccess +} + +/// Gets the icon of the specified app by bundle identifier +/// +/// # Arguments +/// * `client` - A valid SpringBoardServicesClient handle +/// * `bundle_identifier` - The identifiers of the app to get icon +/// * `out_result` - On success, will be set to point to a newly allocated png data +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `out_result` must be a valid, non-null pointer to a location where the result will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn springboard_services_proxy_get_icon( + client: *mut SpringBoardServicesClientHandle, + bundle_identifier: *const libc::c_char, + out_result: *mut *mut c_void, + out_result_len: *mut libc::size_t, +) -> IdeviceErrorCode { + if client.is_null() || out_result.is_null() || out_result_len.is_null() { + log::error!("Invalid arguments: {client:?}, {out_result:?}"); + return IdeviceErrorCode::InvalidArg; + } + let client = unsafe { &mut *client }; + + + let name_cstr = unsafe { CStr::from_ptr(bundle_identifier) }; + let bundle_id = match name_cstr.to_str() { + Ok(s) => s.to_string(), + Err(_) => return IdeviceErrorCode::InvalidArg, + }; + + let res: Result, IdeviceError> = RUNTIME.block_on(async { + client.0.get_icon_pngdata(bundle_id).await + }); + + match res { + Ok(r) => { + let len = r.len(); + let boxed_slice = r.into_boxed_slice(); + let ptr = boxed_slice.as_ptr(); + std::mem::forget(boxed_slice); + + unsafe { + *out_result = ptr as *mut c_void; + *out_result_len = len; + } + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + + + + + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn springboard_services_proxy_free( + handle: *mut SpringBoardServicesClientHandle, +) { + if !handle.is_null() { + log::debug!("Freeing springboard_services_proxy_client"); + let _ = unsafe { Box::from_raw(handle) }; + } +} diff --git a/idevice/Cargo.toml b/idevice/Cargo.toml index c1c73ac..1e94549 100644 --- a/idevice/Cargo.toml +++ b/idevice/Cargo.toml @@ -48,6 +48,7 @@ debug_proxy = [] dvt = ["dep:byteorder", "dep:ns-keyed-archive"] heartbeat = [] installation_proxy = [] +sbservices = [] misagent = [] mounter = ["dep:sha2"] tcp = ["tokio/net"] @@ -77,6 +78,7 @@ full = [ "tunnel_tcp_stack", "tss", "tunneld", + "sbservices" ] # Why: https://github.com/rust-lang/cargo/issues/1197 diff --git a/idevice/src/lib.rs b/idevice/src/lib.rs index 7f57294..5634823 100644 --- a/idevice/src/lib.rs +++ b/idevice/src/lib.rs @@ -30,6 +30,8 @@ pub mod usbmuxd; mod util; #[cfg(feature = "xpc")] pub mod xpc; +#[cfg(feature = "sbservices")] +pub mod sbservices; use log::{debug, error, trace}; use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; diff --git a/idevice/src/sbservices/mod.rs b/idevice/src/sbservices/mod.rs new file mode 100644 index 0000000..ed40723 --- /dev/null +++ b/idevice/src/sbservices/mod.rs @@ -0,0 +1,60 @@ +use crate::{lockdownd::LockdowndClient, Idevice, IdeviceError, IdeviceService}; + +pub struct SpringBoardServicesClient { + pub idevice: Idevice +} + +impl IdeviceService for SpringBoardServicesClient { + fn service_name() -> &'static str { + "com.apple.springboardservices" + } + + async fn connect( + provider: &dyn crate::provider::IdeviceProvider, + ) -> Result { + let mut lockdown = LockdowndClient::connect(provider).await?; + lockdown + .start_session(&provider.get_pairing_file().await?) + .await?; + + let (port, ssl) = lockdown.start_service(Self::service_name()).await?; + + let mut idevice = provider.connect(port).await?; + if ssl { + idevice + .start_session(&provider.get_pairing_file().await?) + .await?; + } + + Ok(Self { idevice }) + } +} + +impl SpringBoardServicesClient { + pub fn new(idevice: Idevice) -> Self { + Self { idevice } + } + + /// Gets the icon of a spceified app + /// # Arguments + /// `bundle_identifier` - The identifier of the app to get icon + pub async fn get_icon_pngdata( + &mut self, + bundle_identifier: String + ) -> Result, IdeviceError> { + let mut req = plist::Dictionary::new(); + req.insert("command".into(), "getIconPNGData".into()); + req.insert("bundleId".into(), bundle_identifier.into()); + self.idevice + .send_plist(plist::Value::Dictionary(req)) + .await?; + + let mut res = self.idevice.read_plist().await?; + match res.remove("pngData") { + Some(plist::Value::Data(res)) => { + Ok(res) + } + _ => Err(IdeviceError::UnexpectedResponse), + } + } +}