diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index 40e3dfb..1503fdb 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -32,8 +32,8 @@ endif() # ---- Build the C++ wrapper library ----------------------------------------- # Collect your .cpps -file(GLOB IDEVICE_CPP_SOURCES - ${IDEVICE_CPP_SRC_DIR}/*.cpp +file(GLOB_RECURSE IDEVICE_CPP_SOURCES + "${IDEVICE_CPP_SRC_DIR}/*.cpp" ) file(GLOB PLIST_CPP_SOURCES diff --git a/cpp/examples/location_simulation.cpp b/cpp/examples/location_simulation.cpp index b458faa..e154dab 100644 --- a/cpp/examples/location_simulation.cpp +++ b/cpp/examples/location_simulation.cpp @@ -6,11 +6,11 @@ #include #include +#include +#include #include -#include #include #include -#include #include #include diff --git a/cpp/examples/screenshot.cpp b/cpp/examples/screenshot.cpp new file mode 100644 index 0000000..dc48e9f --- /dev/null +++ b/cpp/examples/screenshot.cpp @@ -0,0 +1,106 @@ +// Jackson Coxson + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace IdeviceFFI; + +[[noreturn]] +static void die(const char* msg, const FfiError& e) { + std::cerr << msg << ": " << e.message << "\n"; + std::exit(1); +} + +int main(int argc, char** argv) { + // Usage: + // take_screenshot + if (argc != 2) { + std::cerr << "Usage:\n" + << " " << argv[0] << " \n"; + return 2; + } + + std::string out_path = argv[1]; + + // 1) Connect to usbmuxd and pick first device + auto mux = UsbmuxdConnection::default_new(/*tag*/ 0).expect("failed to connect to usbmuxd"); + + auto devices = mux.get_devices().expect("failed to list devices"); + if (devices.empty()) { + std::cerr << "no devices connected\n"; + return 1; + } + auto& dev = (devices)[0]; + + auto udid = dev.get_udid(); + if (udid.is_none()) { + std::cerr << "device has no UDID\n"; + return 1; + } + auto mux_id = dev.get_id(); + if (mux_id.is_none()) { + std::cerr << "device has no mux id\n"; + return 1; + } + + // 2) Provider via default usbmuxd addr + auto addr = UsbmuxdAddr::default_new(); + const uint32_t tag = 0; + const std::string label = "screenshot-client"; + + auto provider = + Provider::usbmuxd_new(std::move(addr), tag, udid.unwrap(), mux_id.unwrap(), label) + .expect("failed to create provider"); + + // 3) CoreDeviceProxy + auto cdp = CoreDeviceProxy::connect(provider).unwrap_or_else( + [](FfiError e) -> CoreDeviceProxy { die("failed to connect CoreDeviceProxy", e); }); + + auto rsd_port = cdp.get_server_rsd_port().unwrap_or_else( + [](FfiError err) -> uint16_t { die("failed to get server RSD port", err); }); + + // 4) Create software tunnel adapter (consumes proxy) + auto adapter = + std::move(cdp).create_tcp_adapter().expect("failed to create software tunnel adapter"); + + // 5) Connect adapter to RSD → ReadWrite stream + auto stream = adapter.connect(rsd_port).expect("failed to connect RSD stream"); + + // 6) RSD handshake (consumes stream) + auto rsd = RsdHandshake::from_socket(std::move(stream)).expect("failed RSD handshake"); + + // 7) RemoteServer over RSD (borrows adapter + handshake) + auto rs = RemoteServer::connect_rsd(adapter, rsd).expect("failed to connect to RemoteServer"); + + // 8) ScreenshotClient (borrows RemoteServer) + auto ss = ScreenshotClient::create(rs).unwrap_or_else( + [](FfiError e) -> ScreenshotClient { die("failed to create ScreenshotClient", e); }); + + // 9) Capture screenshot + auto buf = ss.capture().unwrap_or_else( + [](FfiError e) -> std::vector { die("failed to capture screenshot", e); }); + + // 10) Write PNG file + std::ofstream out(out_path, std::ios::binary); + if (!out.is_open()) { + std::cerr << "failed to open output file: " << out_path << "\n"; + return 1; + } + + out.write(reinterpret_cast(buf.data()), static_cast(buf.size())); + out.close(); + + std::cout << "Screenshot saved to " << out_path << " (" << buf.size() << " bytes)\n"; + return 0; +} diff --git a/cpp/include/idevice++/location_simulation.hpp b/cpp/include/idevice++/dvt/location_simulation.hpp similarity index 96% rename from cpp/include/idevice++/location_simulation.hpp rename to cpp/include/idevice++/dvt/location_simulation.hpp index be4dd85..04fc3e0 100644 --- a/cpp/include/idevice++/location_simulation.hpp +++ b/cpp/include/idevice++/dvt/location_simulation.hpp @@ -2,7 +2,7 @@ #pragma once #include -#include +#include #include #include diff --git a/cpp/include/idevice++/process_control.hpp b/cpp/include/idevice++/dvt/process_control.hpp similarity index 97% rename from cpp/include/idevice++/process_control.hpp rename to cpp/include/idevice++/dvt/process_control.hpp index 806dcfb..9f4cc8e 100644 --- a/cpp/include/idevice++/process_control.hpp +++ b/cpp/include/idevice++/dvt/process_control.hpp @@ -2,7 +2,7 @@ #pragma once #include -#include +#include #include #include diff --git a/cpp/include/idevice++/remote_server.hpp b/cpp/include/idevice++/dvt/remote_server.hpp similarity index 100% rename from cpp/include/idevice++/remote_server.hpp rename to cpp/include/idevice++/dvt/remote_server.hpp diff --git a/cpp/include/idevice++/dvt/screenshot.hpp b/cpp/include/idevice++/dvt/screenshot.hpp new file mode 100644 index 0000000..7e38fd9 --- /dev/null +++ b/cpp/include/idevice++/dvt/screenshot.hpp @@ -0,0 +1,49 @@ +// Jackson Coxson + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace IdeviceFFI { + +using ScreenshotClientPtr = + std::unique_ptr>; + +/// C++ wrapper around the ScreenshotClient FFI handle +/// +/// Provides a high-level interface for capturing screenshots +/// from a connected iOS device through the DVT service. +class ScreenshotClient { + public: + /// Creates a new ScreenshotClient using an existing RemoteServer. + /// + /// The RemoteServer is borrowed, not consumed. + static Result create(RemoteServer& server); + + /// Captures a screenshot and returns it as a PNG buffer. + /// + /// On success, returns a vector containing PNG-encoded bytes. + Result, FfiError> capture(); + + ~ScreenshotClient() noexcept = default; + ScreenshotClient(ScreenshotClient&&) noexcept = default; + ScreenshotClient& operator=(ScreenshotClient&&) noexcept = default; + ScreenshotClient(const ScreenshotClient&) = delete; + ScreenshotClient& operator=(const ScreenshotClient&) = delete; + + ScreenshotClientHandle* raw() const noexcept { return handle_.get(); } + static ScreenshotClient adopt(ScreenshotClientHandle* h) noexcept { + return ScreenshotClient(h); + } + + private: + explicit ScreenshotClient(ScreenshotClientHandle* h) noexcept : handle_(h) {} + ScreenshotClientPtr handle_{}; +}; + +} // namespace IdeviceFFI diff --git a/cpp/src/location_simulation.cpp b/cpp/src/dvt/location_simulation.cpp similarity index 94% rename from cpp/src/location_simulation.cpp rename to cpp/src/dvt/location_simulation.cpp index 99abb85..4234cf9 100644 --- a/cpp/src/location_simulation.cpp +++ b/cpp/src/dvt/location_simulation.cpp @@ -1,6 +1,6 @@ // Jackson Coxson -#include +#include namespace IdeviceFFI { diff --git a/cpp/src/process_control.cpp b/cpp/src/dvt/process_control.cpp similarity index 98% rename from cpp/src/process_control.cpp rename to cpp/src/dvt/process_control.cpp index 2764aac..c234362 100644 --- a/cpp/src/process_control.cpp +++ b/cpp/src/dvt/process_control.cpp @@ -1,6 +1,6 @@ // Jackson Coxson -#include +#include namespace IdeviceFFI { diff --git a/cpp/src/remote_server.cpp b/cpp/src/dvt/remote_server.cpp similarity index 94% rename from cpp/src/remote_server.cpp rename to cpp/src/dvt/remote_server.cpp index acfc736..bdace1a 100644 --- a/cpp/src/remote_server.cpp +++ b/cpp/src/dvt/remote_server.cpp @@ -1,6 +1,6 @@ // Jackson Coxson -#include +#include namespace IdeviceFFI { diff --git a/cpp/src/dvt/screenshot.cpp b/cpp/src/dvt/screenshot.cpp new file mode 100644 index 0000000..fae32c9 --- /dev/null +++ b/cpp/src/dvt/screenshot.cpp @@ -0,0 +1,37 @@ +// Jackson Coxson + +#include + +namespace IdeviceFFI { + +Result ScreenshotClient::create(RemoteServer& server) { + ScreenshotClientHandle* out = nullptr; + FfiError e(::screenshot_client_new(server.raw(), &out)); + if (e) { + return Err(e); + } + return Ok(ScreenshotClient::adopt(out)); +} + +Result, FfiError> ScreenshotClient::capture() { + uint8_t* data = nullptr; + size_t len = 0; + + FfiError e(::screenshot_client_clear(handle_.get(), &data, &len)); + if (e) { + return Err(e); + } + + // Copy into a C++ buffer + std::vector out(len); + if (len > 0 && data != nullptr) { + std::memcpy(out.data(), data, len); + } + + // Free Rust-allocated data + ::idevice_data_free(data, len); + + return Ok(std::move(out)); +} + +} // namespace IdeviceFFI diff --git a/ffi/src/location_simulation.rs b/ffi/src/dvt/location_simulation.rs similarity index 97% rename from ffi/src/location_simulation.rs rename to ffi/src/dvt/location_simulation.rs index 841840a..2bd5488 100644 --- a/ffi/src/location_simulation.rs +++ b/ffi/src/dvt/location_simulation.rs @@ -4,7 +4,7 @@ use std::ptr::null_mut; use idevice::{ReadWrite, dvt::location_simulation::LocationSimulationClient}; -use crate::{IdeviceFfiError, RUNTIME, ffi_err, remote_server::RemoteServerHandle}; +use crate::{IdeviceFfiError, RUNTIME, dvt::remote_server::RemoteServerHandle, ffi_err}; /// Opaque handle to a ProcessControlClient pub struct LocationSimulationHandle<'a>(pub LocationSimulationClient<'a, Box>); diff --git a/ffi/src/dvt/mod.rs b/ffi/src/dvt/mod.rs new file mode 100644 index 0000000..c321eba --- /dev/null +++ b/ffi/src/dvt/mod.rs @@ -0,0 +1,8 @@ +// Jackson Coxson + +#[cfg(feature = "location_simulation")] +pub mod location_simulation; + +pub mod process_control; +pub mod remote_server; +pub mod screenshot; diff --git a/ffi/src/process_control.rs b/ffi/src/dvt/process_control.rs similarity index 98% rename from ffi/src/process_control.rs rename to ffi/src/dvt/process_control.rs index b771125..f0bbce2 100644 --- a/ffi/src/process_control.rs +++ b/ffi/src/dvt/process_control.rs @@ -8,7 +8,7 @@ use std::{ use idevice::{ReadWrite, dvt::process_control::ProcessControlClient}; use plist::{Dictionary, Value}; -use crate::{IdeviceFfiError, RUNTIME, ffi_err, remote_server::RemoteServerHandle}; +use crate::{IdeviceFfiError, RUNTIME, dvt::remote_server::RemoteServerHandle, ffi_err}; /// Opaque handle to a ProcessControlClient pub struct ProcessControlHandle<'a>(pub ProcessControlClient<'a, Box>); diff --git a/ffi/src/remote_server.rs b/ffi/src/dvt/remote_server.rs similarity index 100% rename from ffi/src/remote_server.rs rename to ffi/src/dvt/remote_server.rs diff --git a/ffi/src/dvt/screenshot.rs b/ffi/src/dvt/screenshot.rs new file mode 100644 index 0000000..bfe9153 --- /dev/null +++ b/ffi/src/dvt/screenshot.rs @@ -0,0 +1,113 @@ +// Jackson Coxson + +use std::ptr::null_mut; + +use idevice::{ReadWrite, dvt::screenshot::ScreenshotClient}; + +use crate::{IdeviceFfiError, RUNTIME, dvt::remote_server::RemoteServerHandle, ffi_err}; + +/// An opaque FFI handle for a [`ScreenshotClient`]. +/// +/// This type wraps a [`ScreenshotClient`] that communicates with +/// a connected device to capture screenshots through the DVT (Device Virtualization Toolkit) service. +pub struct ScreenshotClientHandle<'a>(pub ScreenshotClient<'a, Box>); + +/// Creates a new [`ScreenshotClient`] associated with a given [`RemoteServerHandle`]. +/// +/// # Arguments +/// * `server` - A pointer to a valid [`RemoteServerHandle`], previously created by this library. +/// * `handle` - A pointer to a location where the newly created [`ScreenshotClientHandle`] will be stored. +/// +/// # Returns +/// * `null_mut()` on success. +/// * A pointer to an [`IdeviceFfiError`] on failure. +/// +/// # Safety +/// - `server` must be a non-null pointer to a valid remote server handle allocated by this library. +/// - `handle` must be a non-null pointer to a writable memory location where the handle will be stored. +/// - The returned handle must later be freed using [`screenshot_client_free`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn screenshot_client_new( + server: *mut RemoteServerHandle, + handle: *mut *mut ScreenshotClientHandle<'static>, +) -> *mut IdeviceFfiError { + if server.is_null() || handle.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let server = unsafe { &mut (*server).0 }; + let res = RUNTIME.block_on(async move { ScreenshotClient::new(server).await }); + + match res { + Ok(client) => { + let boxed = Box::new(ScreenshotClientHandle(client)); + unsafe { *handle = Box::into_raw(boxed) }; + null_mut() + } + Err(e) => ffi_err!(e), + } +} + +/// Frees a [`ScreenshotClientHandle`]. +/// +/// This releases all memory associated with the handle. +/// After calling this function, the handle pointer must not be used again. +/// +/// # Arguments +/// * `handle` - Pointer to a [`ScreenshotClientHandle`] previously returned by [`screenshot_client_new`]. +/// +/// # Safety +/// - `handle` must either be `NULL` or a valid pointer created by this library. +/// - Double-freeing or using the handle after freeing causes undefined behavior. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn screenshot_client_free(handle: *mut ScreenshotClientHandle<'static>) { + if !handle.is_null() { + let _ = unsafe { Box::from_raw(handle) }; + } +} + +/// Captures a screenshot from the connected device. +/// +/// On success, this function writes a pointer to the PNG-encoded screenshot data and its length +/// into the provided output arguments. The caller is responsible for freeing this data using +/// `idevice_data_free`. +/// +/// # Arguments +/// * `handle` - A pointer to a valid [`ScreenshotClientHandle`]. +/// * `data` - Output pointer where the screenshot buffer pointer will be written. +/// * `len` - Output pointer where the buffer length (in bytes) will be written. +/// +/// # Returns +/// * `null_mut()` on success. +/// * A pointer to an [`IdeviceFfiError`] on failure. +/// +/// # Safety +/// - `handle` must be a valid pointer to a [`ScreenshotClientHandle`]. +/// - `data` and `len` must be valid writable pointers. +/// - The data returned through `*data` must be freed by the caller with `idevice_data_free`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn screenshot_client_clear( + handle: *mut ScreenshotClientHandle<'static>, + data: *mut *mut u8, + len: *mut usize, +) -> *mut IdeviceFfiError { + if handle.is_null() || data.is_null() || len.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let client = unsafe { &mut (*handle).0 }; + let res = RUNTIME.block_on(async move { client.take_screenshot().await }); + + match res { + Ok(r) => { + let mut r = r.into_boxed_slice(); + unsafe { + *data = r.as_mut_ptr(); + *len = r.len(); + } + std::mem::forget(r); // Prevent Rust from freeing the buffer + null_mut() + } + Err(e) => ffi_err!(e), + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index f5738ab..b2623dd 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -12,13 +12,13 @@ pub mod core_device; pub mod core_device_proxy; #[cfg(feature = "debug_proxy")] pub mod debug_proxy; +#[cfg(feature = "dvt")] +pub mod dvt; mod errors; #[cfg(feature = "heartbeat")] pub mod heartbeat; #[cfg(feature = "installation_proxy")] pub mod installation_proxy; -#[cfg(feature = "location_simulation")] -pub mod location_simulation; pub mod lockdown; pub mod logging; #[cfg(feature = "misagent")] @@ -28,11 +28,7 @@ pub mod mobile_image_mounter; #[cfg(feature = "syslog_relay")] pub mod os_trace_relay; mod pairing_file; -#[cfg(feature = "dvt")] -pub mod process_control; pub mod provider; -#[cfg(feature = "dvt")] -pub mod remote_server; #[cfg(feature = "xpc")] pub mod rsd; #[cfg(feature = "springboardservices")]