mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
Merge master into rppairing-try2
This commit is contained in:
@@ -1,65 +1,23 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService, lockdown::LockdownClient, mobileactivationd::MobileActivationdClient,
|
||||
provider::IdeviceProvider,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkCommand};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("activation")
|
||||
.about("mobileactivationd")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Manage activation status on an iOS device")
|
||||
.with_subcommand("state", JkCommand::new().help("Gets the activation state"))
|
||||
.with_subcommand(
|
||||
"deactivate",
|
||||
JkCommand::new().help("Deactivates the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(Command::new("state").about("Gets the activation state"))
|
||||
.subcommand(Command::new("deactivate").about("Deactivates the device"))
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("activation - activate the device");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "activation-jkcoxson").await
|
||||
{
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let activation_client = MobileActivationdClient::new(&*provider);
|
||||
let mut lc = LockdownClient::connect(&*provider)
|
||||
.await
|
||||
@@ -74,40 +32,19 @@ async fn main() {
|
||||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
if matches.subcommand_matches("state").is_some() {
|
||||
let s = activation_client.state().await.expect("no state");
|
||||
println!("Activation State: {s}");
|
||||
} else if matches.subcommand_matches("deactivate").is_some() {
|
||||
println!("CAUTION: You are deactivating {udid}, press enter to continue.");
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).ok();
|
||||
activation_client.deactivate().await.expect("no deactivate");
|
||||
// } else if matches.subcommand_matches("accept").is_some() {
|
||||
// amfi_client
|
||||
// .accept_developer_mode()
|
||||
// .await
|
||||
// .expect("Failed to show");
|
||||
// } else if matches.subcommand_matches("status").is_some() {
|
||||
// let status = amfi_client
|
||||
// .get_developer_mode_status()
|
||||
// .await
|
||||
// .expect("Failed to get status");
|
||||
// println!("Enabled: {status}");
|
||||
// } else if let Some(matches) = matches.subcommand_matches("state") {
|
||||
// let uuid: &String = match matches.get_one("uuid") {
|
||||
// Some(u) => u,
|
||||
// None => {
|
||||
// eprintln!("No UUID passed. Invalid usage, pass -h for help");
|
||||
// return;
|
||||
// }
|
||||
// };
|
||||
// let status = amfi_client
|
||||
// .trust_app_signer(uuid)
|
||||
// .await
|
||||
// .expect("Failed to get state");
|
||||
// println!("Enabled: {status}");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
let (sub_name, _sub_args) = arguments.first_subcommand().expect("no subarg passed");
|
||||
|
||||
match sub_name.as_str() {
|
||||
"state" => {
|
||||
let s = activation_client.state().await.expect("no state");
|
||||
println!("Activation State: {s}");
|
||||
}
|
||||
"deactivate" => {
|
||||
println!("CAUTION: You are deactivating {udid}, press enter to continue.");
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).ok();
|
||||
activation_client.deactivate().await.expect("no deactivate");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
304
tools/src/afc.rs
304
tools/src/afc.rs
@@ -2,130 +2,119 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Arg, Command, value_parser};
|
||||
use idevice::{
|
||||
IdeviceService,
|
||||
afc::{AfcClient, opcode::AfcFopenMode},
|
||||
house_arrest::HouseArrestClient,
|
||||
provider::IdeviceProvider,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand, JkFlag};
|
||||
|
||||
mod common;
|
||||
const DOCS_HELP: &str = "Read the documents from a bundle. Note that when vending documents, you can only access files in /Documents";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("afc")
|
||||
.about("Manage files on the device")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Manage files in the AFC jail of a device")
|
||||
.with_flag(
|
||||
JkFlag::new("documents")
|
||||
.with_help(DOCS_HELP)
|
||||
.with_argument(JkArgument::new().required(true)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
.with_flag(
|
||||
JkFlag::new("container")
|
||||
.with_help("Read the container contents of a bundle")
|
||||
.with_argument(JkArgument::new().required(true)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.long("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("documents")
|
||||
.long("documents")
|
||||
.value_name("BUNDLE_ID")
|
||||
.help("Read the documents from a bundle. Note that when vending documents, you can only access files in /Documents")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("container")
|
||||
.long("container")
|
||||
.value_name("BUNDLE_ID")
|
||||
.help("Read the container contents of a bundle")
|
||||
.global(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("list")
|
||||
.about("Lists the items in the directory")
|
||||
.arg(Arg::new("path").required(true).index(1)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("download")
|
||||
.about("Downloads a file")
|
||||
.arg(Arg::new("path").required(true).index(1))
|
||||
.arg(Arg::new("save").required(true).index(2)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("upload")
|
||||
.about("Creates a directory")
|
||||
.arg(
|
||||
Arg::new("file")
|
||||
.with_subcommand(
|
||||
"list",
|
||||
JkCommand::new()
|
||||
.help("Lists the items in the directory")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.index(1)
|
||||
.value_parser(value_parser!(PathBuf)),
|
||||
.with_help("The directory to list in"),
|
||||
),
|
||||
)
|
||||
.with_subcommand(
|
||||
"download",
|
||||
JkCommand::new()
|
||||
.help("Download a file")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path in the AFC jail"),
|
||||
)
|
||||
.arg(Arg::new("path").required(true).index(2)),
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path to save file to"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("mkdir")
|
||||
.about("Creates a directory")
|
||||
.arg(Arg::new("path").required(true).index(1)),
|
||||
.with_subcommand(
|
||||
"upload",
|
||||
JkCommand::new()
|
||||
.help("Upload a file")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path to the file to upload"),
|
||||
)
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path to save file to in the AFC jail"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("remove")
|
||||
.about("Remove a provisioning profile")
|
||||
.arg(Arg::new("path").required(true).index(1)),
|
||||
.with_subcommand(
|
||||
"mkdir",
|
||||
JkCommand::new().help("Create a folder").with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path to the folder to create in the AFC jail"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("remove_all")
|
||||
.about("Remove a provisioning profile")
|
||||
.arg(Arg::new("path").required(true).index(1)),
|
||||
.with_subcommand(
|
||||
"remove",
|
||||
JkCommand::new().help("Remove a file").with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path to the file to remove"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("info")
|
||||
.about("Get info about a file")
|
||||
.arg(Arg::new("path").required(true).index(1)),
|
||||
.with_subcommand(
|
||||
"remove_all",
|
||||
JkCommand::new().help("Remove a folder").with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path to the folder to remove"),
|
||||
),
|
||||
)
|
||||
.subcommand(Command::new("device_info").about("Get info about the device"))
|
||||
.get_matches();
|
||||
.with_subcommand(
|
||||
"info",
|
||||
JkCommand::new()
|
||||
.help("Get info about a file")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path to the file to get info for"),
|
||||
),
|
||||
)
|
||||
.with_subcommand(
|
||||
"device_info",
|
||||
JkCommand::new().help("Get info about the device"),
|
||||
)
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("afc");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "afc-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut afc_client = if let Some(bundle_id) = matches.get_one::<String>("container") {
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut afc_client = if let Some(bundle_id) = arguments.get_flag::<String>("container") {
|
||||
let h = HouseArrestClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Failed to connect to house arrest");
|
||||
h.vend_container(bundle_id)
|
||||
.await
|
||||
.expect("Failed to vend container")
|
||||
} else if let Some(bundle_id) = matches.get_one::<String>("documents") {
|
||||
} else if let Some(bundle_id) = arguments.get_flag::<String>("documents") {
|
||||
let h = HouseArrestClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Failed to connect to house arrest");
|
||||
@@ -138,59 +127,72 @@ async fn main() {
|
||||
.expect("Unable to connect to misagent")
|
||||
};
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("list") {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
let res = afc_client.list_dir(path).await.expect("Failed to read dir");
|
||||
println!("{path}\n{res:#?}");
|
||||
} else if let Some(matches) = matches.subcommand_matches("mkdir") {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
afc_client.mk_dir(path).await.expect("Failed to mkdir");
|
||||
} else if let Some(matches) = matches.subcommand_matches("download") {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
let save = matches.get_one::<String>("save").expect("No path passed");
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().unwrap();
|
||||
let mut sub_args = sub_args.clone();
|
||||
match sub_name.as_str() {
|
||||
"list" => {
|
||||
let path = sub_args.next_argument::<String>().expect("No path passed");
|
||||
let res = afc_client
|
||||
.list_dir(&path)
|
||||
.await
|
||||
.expect("Failed to read dir");
|
||||
println!("{path}\n{res:#?}");
|
||||
}
|
||||
"mkdir" => {
|
||||
let path = sub_args.next_argument::<String>().expect("No path passed");
|
||||
afc_client.mk_dir(path).await.expect("Failed to mkdir");
|
||||
}
|
||||
"download" => {
|
||||
let path = sub_args.next_argument::<String>().expect("No path passed");
|
||||
let save = sub_args.next_argument::<String>().expect("No path passed");
|
||||
|
||||
let mut file = afc_client
|
||||
.open(path, AfcFopenMode::RdOnly)
|
||||
.await
|
||||
.expect("Failed to open");
|
||||
let mut file = afc_client
|
||||
.open(path, AfcFopenMode::RdOnly)
|
||||
.await
|
||||
.expect("Failed to open");
|
||||
|
||||
let res = file.read_entire().await.expect("Failed to read");
|
||||
tokio::fs::write(save, res)
|
||||
.await
|
||||
.expect("Failed to write to file");
|
||||
} else if let Some(matches) = matches.subcommand_matches("upload") {
|
||||
let file = matches.get_one::<PathBuf>("file").expect("No path passed");
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
let res = file.read_entire().await.expect("Failed to read");
|
||||
tokio::fs::write(save, res)
|
||||
.await
|
||||
.expect("Failed to write to file");
|
||||
}
|
||||
"upload" => {
|
||||
let file = sub_args.next_argument::<PathBuf>().expect("No path passed");
|
||||
let path = sub_args.next_argument::<String>().expect("No path passed");
|
||||
|
||||
let bytes = tokio::fs::read(file).await.expect("Failed to read file");
|
||||
let mut file = afc_client
|
||||
.open(path, AfcFopenMode::WrOnly)
|
||||
.await
|
||||
.expect("Failed to open");
|
||||
let bytes = tokio::fs::read(file).await.expect("Failed to read file");
|
||||
let mut file = afc_client
|
||||
.open(path, AfcFopenMode::WrOnly)
|
||||
.await
|
||||
.expect("Failed to open");
|
||||
|
||||
file.write_entire(&bytes)
|
||||
.await
|
||||
.expect("Failed to upload bytes");
|
||||
} else if let Some(matches) = matches.subcommand_matches("remove") {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
afc_client.remove(path).await.expect("Failed to remove");
|
||||
} else if let Some(matches) = matches.subcommand_matches("remove_all") {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
afc_client.remove_all(path).await.expect("Failed to remove");
|
||||
} else if let Some(matches) = matches.subcommand_matches("info") {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
let res = afc_client
|
||||
.get_file_info(path)
|
||||
.await
|
||||
.expect("Failed to get file info");
|
||||
println!("{res:#?}");
|
||||
} else if matches.subcommand_matches("device_info").is_some() {
|
||||
let res = afc_client
|
||||
.get_device_info()
|
||||
.await
|
||||
.expect("Failed to get file info");
|
||||
println!("{res:#?}");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
file.write_entire(&bytes)
|
||||
.await
|
||||
.expect("Failed to upload bytes");
|
||||
}
|
||||
"remove" => {
|
||||
let path = sub_args.next_argument::<String>().expect("No path passed");
|
||||
afc_client.remove(path).await.expect("Failed to remove");
|
||||
}
|
||||
"remove_all" => {
|
||||
let path = sub_args.next_argument::<String>().expect("No path passed");
|
||||
afc_client.remove_all(path).await.expect("Failed to remove");
|
||||
}
|
||||
"info" => {
|
||||
let path = sub_args.next_argument::<String>().expect("No path passed");
|
||||
let res = afc_client
|
||||
.get_file_info(path)
|
||||
.await
|
||||
.expect("Failed to get file info");
|
||||
println!("{res:#?}");
|
||||
}
|
||||
"device_info" => {
|
||||
let res = afc_client
|
||||
.get_device_info()
|
||||
.await
|
||||
.expect("Failed to get file info");
|
||||
println!("{res:#?}");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +1,81 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{IdeviceService, amfi::AmfiClient};
|
||||
use idevice::{IdeviceService, amfi::AmfiClient, provider::IdeviceProvider};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("amfi")
|
||||
.about("Mess with developer mode")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Mess with devleoper mode")
|
||||
.with_subcommand(
|
||||
"show",
|
||||
JkCommand::new().help("Shows the developer mode option in settings"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
.with_subcommand("enable", JkCommand::new().help("Enables developer mode"))
|
||||
.with_subcommand(
|
||||
"accept",
|
||||
JkCommand::new().help("Shows the accept dialogue for developer mode"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
.with_subcommand(
|
||||
"status",
|
||||
JkCommand::new().help("Gets the developer mode status"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
.with_subcommand(
|
||||
"trust",
|
||||
JkCommand::new()
|
||||
.help("Trusts an app signer")
|
||||
.with_argument(JkArgument::new().with_help("UUID").required(true)),
|
||||
)
|
||||
.subcommand(Command::new("show").about("Shows the developer mode option in settings"))
|
||||
.subcommand(Command::new("enable").about("Enables developer mode"))
|
||||
.subcommand(Command::new("accept").about("Shows the accept dialogue for developer mode"))
|
||||
.subcommand(Command::new("status").about("Gets the developer mode status"))
|
||||
.subcommand(
|
||||
Command::new("trust")
|
||||
.about("Trusts an app signer")
|
||||
.arg(Arg::new("uuid").required(true)),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("amfi - manage developer mode");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "amfi-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut amfi_client = AmfiClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Failed to connect to amfi");
|
||||
|
||||
if matches.subcommand_matches("show").is_some() {
|
||||
amfi_client
|
||||
.reveal_developer_mode_option_in_ui()
|
||||
.await
|
||||
.expect("Failed to show");
|
||||
} else if matches.subcommand_matches("enable").is_some() {
|
||||
amfi_client
|
||||
.enable_developer_mode()
|
||||
.await
|
||||
.expect("Failed to show");
|
||||
} else if matches.subcommand_matches("accept").is_some() {
|
||||
amfi_client
|
||||
.accept_developer_mode()
|
||||
.await
|
||||
.expect("Failed to show");
|
||||
} else if matches.subcommand_matches("status").is_some() {
|
||||
let status = amfi_client
|
||||
.get_developer_mode_status()
|
||||
.await
|
||||
.expect("Failed to get status");
|
||||
println!("Enabled: {status}");
|
||||
} else if let Some(matches) = matches.subcommand_matches("state") {
|
||||
let uuid: &String = match matches.get_one("uuid") {
|
||||
Some(u) => u,
|
||||
None => {
|
||||
eprintln!("No UUID passed. Invalid usage, pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let status = amfi_client
|
||||
.trust_app_signer(uuid)
|
||||
.await
|
||||
.expect("Failed to get state");
|
||||
println!("Enabled: {status}");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().expect("No subcommand passed");
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
match sub_name.as_str() {
|
||||
"show" => {
|
||||
amfi_client
|
||||
.reveal_developer_mode_option_in_ui()
|
||||
.await
|
||||
.expect("Failed to show");
|
||||
}
|
||||
"enable" => {
|
||||
amfi_client
|
||||
.enable_developer_mode()
|
||||
.await
|
||||
.expect("Failed to show");
|
||||
}
|
||||
"accept" => {
|
||||
amfi_client
|
||||
.accept_developer_mode()
|
||||
.await
|
||||
.expect("Failed to show");
|
||||
}
|
||||
"status" => {
|
||||
let status = amfi_client
|
||||
.get_developer_mode_status()
|
||||
.await
|
||||
.expect("Failed to get status");
|
||||
println!("Enabled: {status}");
|
||||
}
|
||||
"trust" => {
|
||||
let uuid: String = match sub_args.next_argument() {
|
||||
Some(u) => u,
|
||||
None => {
|
||||
eprintln!("No UUID passed. Invalid usage, pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let status = amfi_client
|
||||
.trust_app_signer(uuid)
|
||||
.await
|
||||
.expect("Failed to get state");
|
||||
println!("Enabled: {status}");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,111 +1,72 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService, RsdService,
|
||||
core_device::{AppServiceClient, OpenStdioSocketClient},
|
||||
core_device_proxy::CoreDeviceProxy,
|
||||
provider::IdeviceProvider,
|
||||
rsd::RsdHandshake,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("remotexpc")
|
||||
.about("Get services from RemoteXPC")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("tunneld")
|
||||
.long("tunneld")
|
||||
.help("Use tunneld")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(Command::new("list").about("Lists the images mounted on the device"))
|
||||
.subcommand(
|
||||
Command::new("launch")
|
||||
.about("Launch the app on the device")
|
||||
.arg(
|
||||
Arg::new("bundle_id")
|
||||
.required(true)
|
||||
.help("The bundle ID to launch"),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Interact with the RemoteXPC app service on the device")
|
||||
.with_subcommand("list", JkCommand::new().help("List apps on the device"))
|
||||
.with_subcommand(
|
||||
"launch",
|
||||
JkCommand::new()
|
||||
.help("Launch an app on the device")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Bundle ID to launch")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(Command::new("processes").about("List the processes running"))
|
||||
.subcommand(
|
||||
Command::new("uninstall").about("Uninstall an app").arg(
|
||||
Arg::new("bundle_id")
|
||||
.required(true)
|
||||
.help("The bundle ID to uninstall"),
|
||||
.with_subcommand(
|
||||
"processes",
|
||||
JkCommand::new().help("List the processes running"),
|
||||
)
|
||||
.with_subcommand(
|
||||
"uninstall",
|
||||
JkCommand::new().help("Uninstall an app").with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Bundle ID to uninstall")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("signal")
|
||||
.about("Send a signal to an app")
|
||||
.arg(Arg::new("pid").required(true).help("PID to send to"))
|
||||
.arg(Arg::new("signal").required(true).help("Signal to send")),
|
||||
.with_subcommand(
|
||||
"signal",
|
||||
JkCommand::new()
|
||||
.help("Uninstall an app")
|
||||
.with_argument(JkArgument::new().with_help("PID to signal").required(true))
|
||||
.with_argument(JkArgument::new().with_help("Signal to send").required(true)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("icon")
|
||||
.about("Send a signal to an app")
|
||||
.arg(
|
||||
Arg::new("bundle_id")
|
||||
.required(true)
|
||||
.help("The bundle ID to fetch"),
|
||||
.with_subcommand(
|
||||
"icon",
|
||||
JkCommand::new()
|
||||
.help("Fetch an icon for an app")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Bundle ID for the app")
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("path")
|
||||
.required(true)
|
||||
.help("The path to save the icon to"),
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Path to save it to")
|
||||
.required(true),
|
||||
)
|
||||
.arg(Arg::new("hw").required(false).help("The height and width"))
|
||||
.arg(Arg::new("scale").required(false).help("The scale")),
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Height and width")
|
||||
.required(true),
|
||||
)
|
||||
.with_argument(JkArgument::new().with_help("Scale").required(true)),
|
||||
)
|
||||
.get_matches();
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("debug_proxy - connect to the debug proxy and run commands");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
let host = matches.get_one::<String>("host");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "app_service-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let proxy = CoreDeviceProxy::connect(&*provider)
|
||||
.await
|
||||
.expect("no core proxy");
|
||||
@@ -123,121 +84,122 @@ async fn main() {
|
||||
.await
|
||||
.expect("no connect");
|
||||
|
||||
if matches.subcommand_matches("list").is_some() {
|
||||
let apps = asc
|
||||
.list_apps(true, true, true, true, true)
|
||||
.await
|
||||
.expect("Failed to get apps");
|
||||
println!("{apps:#?}");
|
||||
} else if let Some(matches) = matches.subcommand_matches("launch") {
|
||||
let bundle_id: &String = match matches.get_one("bundle_id") {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().expect("No subcommand");
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
let mut stdio_conn = OpenStdioSocketClient::connect_rsd(&mut adapter, &mut handshake)
|
||||
.await
|
||||
.expect("no stdio");
|
||||
|
||||
let stdio_uuid = stdio_conn.read_uuid().await.expect("no uuid");
|
||||
println!("stdio uuid: {stdio_uuid:?}");
|
||||
|
||||
let res = asc
|
||||
.launch_application(bundle_id, &[], true, false, None, None, Some(stdio_uuid))
|
||||
.await
|
||||
.expect("no launch");
|
||||
|
||||
println!("Launch response {res:#?}");
|
||||
|
||||
let (mut remote_reader, mut remote_writer) = tokio::io::split(stdio_conn.inner);
|
||||
let mut local_stdin = tokio::io::stdin();
|
||||
let mut local_stdout = tokio::io::stdout();
|
||||
|
||||
tokio::select! {
|
||||
// Task 1: Copy data from the remote process to local stdout
|
||||
res = tokio::io::copy(&mut remote_reader, &mut local_stdout) => {
|
||||
if let Err(e) = res {
|
||||
eprintln!("Error copying from remote to local: {}", e);
|
||||
match sub_name.as_str() {
|
||||
"list" => {
|
||||
let apps = asc
|
||||
.list_apps(true, true, true, true, true)
|
||||
.await
|
||||
.expect("Failed to get apps");
|
||||
println!("{apps:#?}");
|
||||
}
|
||||
"launch" => {
|
||||
let bundle_id: String = match sub_args.next_argument() {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
println!("\nRemote connection closed.");
|
||||
return;
|
||||
}
|
||||
// Task 2: Copy data from local stdin to the remote process
|
||||
res = tokio::io::copy(&mut local_stdin, &mut remote_writer) => {
|
||||
if let Err(e) = res {
|
||||
eprintln!("Error copying from local to remote: {}", e);
|
||||
};
|
||||
|
||||
let mut stdio_conn = OpenStdioSocketClient::connect_rsd(&mut adapter, &mut handshake)
|
||||
.await
|
||||
.expect("no stdio");
|
||||
|
||||
let stdio_uuid = stdio_conn.read_uuid().await.expect("no uuid");
|
||||
println!("stdio uuid: {stdio_uuid:?}");
|
||||
|
||||
let res = asc
|
||||
.launch_application(bundle_id, &[], true, false, None, None, Some(stdio_uuid))
|
||||
.await
|
||||
.expect("no launch");
|
||||
|
||||
println!("Launch response {res:#?}");
|
||||
|
||||
let (mut remote_reader, mut remote_writer) = tokio::io::split(stdio_conn.inner);
|
||||
let mut local_stdin = tokio::io::stdin();
|
||||
let mut local_stdout = tokio::io::stdout();
|
||||
|
||||
tokio::select! {
|
||||
// Task 1: Copy data from the remote process to local stdout
|
||||
res = tokio::io::copy(&mut remote_reader, &mut local_stdout) => {
|
||||
if let Err(e) = res {
|
||||
eprintln!("Error copying from remote to local: {}", e);
|
||||
}
|
||||
println!("\nRemote connection closed.");
|
||||
}
|
||||
// Task 2: Copy data from local stdin to the remote process
|
||||
res = tokio::io::copy(&mut local_stdin, &mut remote_writer) => {
|
||||
if let Err(e) = res {
|
||||
eprintln!("Error copying from local to remote: {}", e);
|
||||
}
|
||||
println!("\nLocal stdin closed.");
|
||||
}
|
||||
println!("\nLocal stdin closed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if matches.subcommand_matches("processes").is_some() {
|
||||
let p = asc.list_processes().await.expect("no processes?");
|
||||
println!("{p:#?}");
|
||||
} else if let Some(matches) = matches.subcommand_matches("uninstall") {
|
||||
let bundle_id: &String = match matches.get_one("bundle_id") {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
"processes" => {
|
||||
let p = asc.list_processes().await.expect("no processes?");
|
||||
println!("{p:#?}");
|
||||
}
|
||||
"uninstall" => {
|
||||
let bundle_id: String = match sub_args.next_argument() {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
asc.uninstall_app(bundle_id).await.expect("no launch")
|
||||
} else if let Some(matches) = matches.subcommand_matches("signal") {
|
||||
let pid: u32 = match matches.get_one::<String>("pid") {
|
||||
Some(b) => b.parse().expect("failed to parse PID as u32"),
|
||||
None => {
|
||||
eprintln!("No bundle PID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let signal: u32 = match matches.get_one::<String>("signal") {
|
||||
Some(b) => b.parse().expect("failed to parse signal as u32"),
|
||||
None => {
|
||||
eprintln!("No bundle signal passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
asc.uninstall_app(bundle_id).await.expect("no launch")
|
||||
}
|
||||
"signal" => {
|
||||
let pid: u32 = match sub_args.next_argument() {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle PID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let signal: u32 = match sub_args.next_argument() {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle signal passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let res = asc.send_signal(pid, signal).await.expect("no signal");
|
||||
println!("{res:#?}");
|
||||
} else if let Some(matches) = matches.subcommand_matches("icon") {
|
||||
let bundle_id: &String = match matches.get_one("bundle_id") {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let save_path: &String = match matches.get_one("path") {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let hw: f32 = match matches.get_one::<String>("hw") {
|
||||
Some(b) => b.parse().expect("failed to parse PID as f32"),
|
||||
None => 1.0,
|
||||
};
|
||||
let scale: f32 = match matches.get_one::<String>("scale") {
|
||||
Some(b) => b.parse().expect("failed to parse signal as f32"),
|
||||
None => 1.0,
|
||||
};
|
||||
let res = asc.send_signal(pid, signal).await.expect("no signal");
|
||||
println!("{res:#?}");
|
||||
}
|
||||
"icon" => {
|
||||
let bundle_id: String = match sub_args.next_argument() {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let save_path: String = match sub_args.next_argument() {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let hw: f32 = sub_args.next_argument().unwrap_or(1.0);
|
||||
let scale: f32 = sub_args.next_argument().unwrap_or(1.0);
|
||||
|
||||
let res = asc
|
||||
.fetch_app_icon(bundle_id, hw, hw, scale, true)
|
||||
.await
|
||||
.expect("no signal");
|
||||
println!("{res:?}");
|
||||
tokio::fs::write(save_path, res.data)
|
||||
.await
|
||||
.expect("failed to save");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
let res = asc
|
||||
.fetch_app_icon(bundle_id, hw, hw, scale, true)
|
||||
.await
|
||||
.expect("no signal");
|
||||
println!("{res:?}");
|
||||
tokio::fs::write(save_path, res.data)
|
||||
.await
|
||||
.expect("failed to save");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,20 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use futures_util::StreamExt;
|
||||
use idevice::{IdeviceService, bt_packet_logger::BtPacketLoggerClient};
|
||||
use idevice::{IdeviceService, bt_packet_logger::BtPacketLoggerClient, provider::IdeviceProvider};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
use tokio::io::AsyncWrite;
|
||||
|
||||
use crate::pcap::{write_pcap_header, write_pcap_record};
|
||||
|
||||
mod common;
|
||||
mod pcap;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Writes Bluetooth pcap data")
|
||||
.with_argument(JkArgument::new().with_help("Write PCAP to this file (use '-' for stdout)"))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("amfi")
|
||||
.about("Capture Bluetooth packets")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("out")
|
||||
.long("out")
|
||||
.value_name("PCAP")
|
||||
.help("Write PCAP to this file (use '-' for stdout)"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("bt_packet_logger - capture bluetooth packets");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
let out = matches.get_one::<String>("out").map(String::to_owned);
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "amfi-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let out: Option<String> = arguments.clone().next_argument();
|
||||
|
||||
let logger_client = BtPacketLoggerClient::connect(&*provider)
|
||||
.await
|
||||
|
||||
@@ -1,83 +1,60 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command, arg};
|
||||
use idevice::{
|
||||
IdeviceService, RsdService, companion_proxy::CompanionProxy,
|
||||
core_device_proxy::CoreDeviceProxy, pretty_print_dictionary, pretty_print_plist,
|
||||
rsd::RsdHandshake,
|
||||
core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider, rsd::RsdHandshake,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
use plist_macro::{pretty_print_dictionary, pretty_print_plist};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("companion_proxy")
|
||||
.about("Apple Watch things")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(Command::new("list").about("List the companions on the device"))
|
||||
.subcommand(Command::new("listen").about("Listen for devices"))
|
||||
.subcommand(
|
||||
Command::new("get")
|
||||
.about("Gets a value")
|
||||
.arg(arg!(-d --device_udid <STRING> "the device udid to get from").required(true))
|
||||
.arg(arg!(-v --value <STRING> "the value to get").required(true)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("start")
|
||||
.about("Starts a service")
|
||||
.arg(arg!(-p --port <PORT> "the port").required(true))
|
||||
.arg(arg!(-n --name <STRING> "the optional service name").required(false)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("stop")
|
||||
.about("Starts a service")
|
||||
.arg(arg!(-p --port <PORT> "the port").required(true)),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("companion_proxy");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "amfi-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Apple Watch proxy")
|
||||
.with_subcommand(
|
||||
"list",
|
||||
JkCommand::new().help("List the companions on the device"),
|
||||
)
|
||||
.with_subcommand("listen", JkCommand::new().help("Listen for devices"))
|
||||
.with_subcommand(
|
||||
"get",
|
||||
JkCommand::new()
|
||||
.help("Gets a value from an AW")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("The AW UDID to get from")
|
||||
.required(true),
|
||||
)
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("The value to get")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.with_subcommand(
|
||||
"start",
|
||||
JkCommand::new()
|
||||
.help("Starts a service on the Apple Watch")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("The port to listen on")
|
||||
.required(true),
|
||||
)
|
||||
.with_argument(JkArgument::new().with_help("The service name")),
|
||||
)
|
||||
.with_subcommand(
|
||||
"stop",
|
||||
JkCommand::new()
|
||||
.help("Stops a service on the Apple Watch")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("The port to stop")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let proxy = CoreDeviceProxy::connect(&*provider)
|
||||
.await
|
||||
.expect("no core_device_proxy");
|
||||
@@ -97,55 +74,72 @@ async fn main() {
|
||||
// .await
|
||||
// .expect("Failed to connect to companion proxy");
|
||||
|
||||
if matches.subcommand_matches("list").is_some() {
|
||||
proxy.get_device_registry().await.expect("Failed to show");
|
||||
} else if matches.subcommand_matches("listen").is_some() {
|
||||
let mut stream = proxy.listen_for_devices().await.expect("Failed to show");
|
||||
while let Ok(v) = stream.next().await {
|
||||
println!("{}", pretty_print_dictionary(&v));
|
||||
}
|
||||
} else if let Some(matches) = matches.subcommand_matches("get") {
|
||||
let key = matches.get_one::<String>("value").expect("no value passed");
|
||||
let udid = matches
|
||||
.get_one::<String>("device_udid")
|
||||
.expect("no AW udid passed");
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().unwrap();
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
match proxy.get_value(udid, key).await {
|
||||
Ok(value) => {
|
||||
println!("{}", pretty_print_plist(&value));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error getting value: {e}");
|
||||
match sub_name.as_str() {
|
||||
"list" => {
|
||||
proxy.get_device_registry().await.expect("Failed to show");
|
||||
}
|
||||
"listen" => {
|
||||
let mut stream = proxy.listen_for_devices().await.expect("Failed to show");
|
||||
while let Ok(v) = stream.next().await {
|
||||
println!("{}", pretty_print_dictionary(&v));
|
||||
}
|
||||
}
|
||||
} else if let Some(matches) = matches.subcommand_matches("start") {
|
||||
let port: u16 = matches
|
||||
.get_one::<String>("port")
|
||||
.expect("no port passed")
|
||||
.parse()
|
||||
.expect("not a number");
|
||||
let name = matches.get_one::<String>("name").map(|x| x.as_str());
|
||||
"get" => {
|
||||
let key: String = sub_args.next_argument::<String>().expect("no value passed");
|
||||
let udid = sub_args
|
||||
.next_argument::<String>()
|
||||
.expect("no AW udid passed");
|
||||
|
||||
match proxy.start_forwarding_service_port(port, name, None).await {
|
||||
Ok(value) => {
|
||||
println!("started on port {value}");
|
||||
match proxy.get_value(udid, key).await {
|
||||
Ok(value) => {
|
||||
println!("{}", pretty_print_plist(&value));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error getting value: {e}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
}
|
||||
"start" => {
|
||||
let port: u16 = sub_args
|
||||
.next_argument::<String>()
|
||||
.expect("no port passed")
|
||||
.parse()
|
||||
.expect("not a number");
|
||||
let name = sub_args.next_argument::<String>();
|
||||
|
||||
match proxy
|
||||
.start_forwarding_service_port(
|
||||
port,
|
||||
match &name {
|
||||
Some(n) => Some(n.as_str()),
|
||||
None => None,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(value) => {
|
||||
println!("started on port {value}");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error starting: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
"stop" => {
|
||||
let port: u16 = sub_args
|
||||
.next_argument::<String>()
|
||||
.expect("no port passed")
|
||||
.parse()
|
||||
.expect("not a number");
|
||||
|
||||
if let Err(e) = proxy.stop_forwarding_service_port(port).await {
|
||||
eprintln!("Error starting: {e}");
|
||||
}
|
||||
}
|
||||
} else if let Some(matches) = matches.subcommand_matches("stop") {
|
||||
let port: u16 = matches
|
||||
.get_one::<String>("port")
|
||||
.expect("no port passed")
|
||||
.parse()
|
||||
.expect("not a number");
|
||||
|
||||
if let Err(e) = proxy.stop_forwarding_service_port(port).await {
|
||||
eprintln!("Error starting: {e}");
|
||||
}
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
_ => unreachable!(),
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,96 +1,79 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService,
|
||||
crashreportcopymobile::{CrashReportCopyMobileClient, flush_reports},
|
||||
provider::IdeviceProvider,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
mod common;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Manage crash logs")
|
||||
.with_subcommand(
|
||||
"list",
|
||||
JkCommand::new()
|
||||
.help("List crash logs in the directory")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Path to list in")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.with_subcommand(
|
||||
"flush",
|
||||
JkCommand::new().help("Flushes reports to the directory"),
|
||||
)
|
||||
.with_subcommand(
|
||||
"pull",
|
||||
JkCommand::new()
|
||||
.help("Check the capabilities")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Path to the log to pull")
|
||||
.required(true),
|
||||
)
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Path to save the log to")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("crash_logs")
|
||||
.about("Manage crash logs")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("list")
|
||||
.about("Lists the items in the directory")
|
||||
.arg(Arg::new("dir").required(false).index(1)),
|
||||
)
|
||||
.subcommand(Command::new("flush").about("Flushes reports to the directory"))
|
||||
.subcommand(
|
||||
Command::new("pull")
|
||||
.about("Pulls a log")
|
||||
.arg(Arg::new("path").required(true).index(1))
|
||||
.arg(Arg::new("save").required(true).index(2))
|
||||
.arg(Arg::new("dir").required(false).index(3)),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("crash_logs - manage crash logs on the device");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "afc-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut crash_client = CrashReportCopyMobileClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Unable to connect to misagent");
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("list") {
|
||||
let dir_path: Option<&String> = matches.get_one("dir");
|
||||
let res = crash_client
|
||||
.ls(dir_path.map(|x| x.as_str()))
|
||||
.await
|
||||
.expect("Failed to read dir");
|
||||
println!("{res:#?}");
|
||||
} else if matches.subcommand_matches("flush").is_some() {
|
||||
flush_reports(&*provider).await.expect("Failed to flush");
|
||||
} else if let Some(matches) = matches.subcommand_matches("pull") {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
let save = matches.get_one::<String>("save").expect("No path passed");
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().expect("No sub command passed");
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
let res = crash_client.pull(path).await.expect("Failed to pull log");
|
||||
tokio::fs::write(save, res)
|
||||
.await
|
||||
.expect("Failed to write to file");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
match sub_name.as_str() {
|
||||
"list" => {
|
||||
let dir_path: Option<String> = sub_args.next_argument();
|
||||
let res = crash_client
|
||||
.ls(match &dir_path {
|
||||
Some(d) => Some(d.as_str()),
|
||||
None => None,
|
||||
})
|
||||
.await
|
||||
.expect("Failed to read dir");
|
||||
println!("{res:#?}");
|
||||
}
|
||||
"flush" => {
|
||||
flush_reports(&*provider).await.expect("Failed to flush");
|
||||
}
|
||||
"pull" => {
|
||||
let path = sub_args.next_argument::<String>().expect("No path passed");
|
||||
let save = sub_args.next_argument::<String>().expect("No path passed");
|
||||
|
||||
let res = crash_client.pull(path).await.expect("Failed to pull log");
|
||||
tokio::fs::write(save, res)
|
||||
.await
|
||||
.expect("Failed to write to file");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,70 +2,17 @@
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, debug_proxy::DebugProxyClient,
|
||||
rsd::RsdHandshake,
|
||||
provider::IdeviceProvider, rsd::RsdHandshake,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkCommand};
|
||||
|
||||
mod common;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new().help("Start a debug proxy shell")
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("remotexpc")
|
||||
.about("Get services from RemoteXPC")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("tunneld")
|
||||
.long("tunneld")
|
||||
.help("Use tunneld")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("debug_proxy - connect to the debug proxy and run commands");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
let host = matches.get_one::<String>("host");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "debug-proxy-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(_arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let proxy = CoreDeviceProxy::connect(&*provider)
|
||||
.await
|
||||
.expect("no core proxy");
|
||||
|
||||
@@ -1,106 +1,71 @@
|
||||
// Jackson Coxson
|
||||
// idevice Rust implementation of libimobiledevice's idevicediagnostics
|
||||
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use idevice::{IdeviceService, services::diagnostics_relay::DiagnosticsRelayClient};
|
||||
use idevice::{
|
||||
IdeviceService, provider::IdeviceProvider, services::diagnostics_relay::DiagnosticsRelayClient,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand, JkFlag};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("idevicediagnostics")
|
||||
.about("Interact with the diagnostics interface of a device")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("ioregistry")
|
||||
.about("Print IORegistry information")
|
||||
.arg(
|
||||
Arg::new("plane")
|
||||
.long("plane")
|
||||
.value_name("PLANE")
|
||||
.help("IORegistry plane to query (e.g., IODeviceTree, IOService)"),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Interact with the diagnostics interface of a device")
|
||||
.with_subcommand(
|
||||
"ioregistry",
|
||||
JkCommand::new()
|
||||
.help("Print IORegistry information")
|
||||
.with_flag(
|
||||
JkFlag::new("plane")
|
||||
.with_help("IORegistry plane to query (e.g., IODeviceTree, IOService)")
|
||||
.with_argument(JkArgument::new().required(true)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("name")
|
||||
.long("name")
|
||||
.value_name("NAME")
|
||||
.help("Entry name to filter by"),
|
||||
.with_flag(
|
||||
JkFlag::new("name")
|
||||
.with_help("Entry name to filter by")
|
||||
.with_argument(JkArgument::new().required(true)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("class")
|
||||
.long("class")
|
||||
.value_name("CLASS")
|
||||
.help("Entry class to filter by"),
|
||||
.with_flag(
|
||||
JkFlag::new("class")
|
||||
.with_help("Entry class to filter by")
|
||||
.with_argument(JkArgument::new().required(true)),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("mobilegestalt")
|
||||
.about("Print MobileGestalt information")
|
||||
.arg(
|
||||
Arg::new("keys")
|
||||
.long("keys")
|
||||
.value_name("KEYS")
|
||||
.help("Comma-separated list of keys to query")
|
||||
.value_delimiter(',')
|
||||
.num_args(1..),
|
||||
.with_subcommand(
|
||||
"mobilegestalt",
|
||||
JkCommand::new()
|
||||
.help("Print MobileGestalt information")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Comma-separated list of keys to query")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(Command::new("gasguage").about("Print gas gauge (battery) information"))
|
||||
.subcommand(Command::new("nand").about("Print NAND flash information"))
|
||||
.subcommand(Command::new("all").about("Print all available diagnostics information"))
|
||||
.subcommand(Command::new("wifi").about("Print WiFi diagnostics information"))
|
||||
.subcommand(Command::new("goodbye").about("Send Goodbye to diagnostics relay"))
|
||||
.subcommand(Command::new("restart").about("Restart the device"))
|
||||
.subcommand(Command::new("shutdown").about("Shutdown the device"))
|
||||
.subcommand(Command::new("sleep").about("Put the device to sleep"))
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!(
|
||||
"idevicediagnostics - interact with the diagnostics interface of a device. Reimplementation of libimobiledevice's binary."
|
||||
);
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "idevicediagnostics-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
.with_subcommand(
|
||||
"gasguage",
|
||||
JkCommand::new().help("Print gas gauge (battery) information"),
|
||||
)
|
||||
.with_subcommand(
|
||||
"nand",
|
||||
JkCommand::new().help("Print NAND flash information"),
|
||||
)
|
||||
.with_subcommand(
|
||||
"all",
|
||||
JkCommand::new().help("Print all available diagnostics information"),
|
||||
)
|
||||
.with_subcommand(
|
||||
"wifi",
|
||||
JkCommand::new().help("Print WiFi diagnostics information"),
|
||||
)
|
||||
.with_subcommand(
|
||||
"goodbye",
|
||||
JkCommand::new().help("Send Goodbye to diagnostics relay"),
|
||||
)
|
||||
.with_subcommand("restart", JkCommand::new().help("Restart the device"))
|
||||
.with_subcommand("shutdown", JkCommand::new().help("Shutdown the device"))
|
||||
.with_subcommand("sleep", JkCommand::new().help("Put the device to sleep"))
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut diagnostics_client = match DiagnosticsRelayClient::connect(&*provider).await {
|
||||
Ok(client) => client,
|
||||
Err(e) => {
|
||||
@@ -109,47 +74,52 @@ async fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
match matches.subcommand() {
|
||||
Some(("ioregistry", sub_matches)) => {
|
||||
handle_ioregistry(&mut diagnostics_client, sub_matches).await;
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().unwrap();
|
||||
let mut sub_matches = sub_args.clone();
|
||||
|
||||
match sub_name.as_str() {
|
||||
"ioregistry" => {
|
||||
handle_ioregistry(&mut diagnostics_client, &sub_matches).await;
|
||||
}
|
||||
Some(("mobilegestalt", sub_matches)) => {
|
||||
handle_mobilegestalt(&mut diagnostics_client, sub_matches).await;
|
||||
"mobilegestalt" => {
|
||||
handle_mobilegestalt(&mut diagnostics_client, &mut sub_matches).await;
|
||||
}
|
||||
Some(("gasguage", _)) => {
|
||||
"gasguage" => {
|
||||
handle_gasguage(&mut diagnostics_client).await;
|
||||
}
|
||||
Some(("nand", _)) => {
|
||||
"nand" => {
|
||||
handle_nand(&mut diagnostics_client).await;
|
||||
}
|
||||
Some(("all", _)) => {
|
||||
"all" => {
|
||||
handle_all(&mut diagnostics_client).await;
|
||||
}
|
||||
Some(("wifi", _)) => {
|
||||
"wifi" => {
|
||||
handle_wifi(&mut diagnostics_client).await;
|
||||
}
|
||||
Some(("restart", _)) => {
|
||||
"restart" => {
|
||||
handle_restart(&mut diagnostics_client).await;
|
||||
}
|
||||
Some(("shutdown", _)) => {
|
||||
"shutdown" => {
|
||||
handle_shutdown(&mut diagnostics_client).await;
|
||||
}
|
||||
Some(("sleep", _)) => {
|
||||
"sleep" => {
|
||||
handle_sleep(&mut diagnostics_client).await;
|
||||
}
|
||||
Some(("goodbye", _)) => {
|
||||
"goodbye" => {
|
||||
handle_goodbye(&mut diagnostics_client).await;
|
||||
}
|
||||
_ => {
|
||||
eprintln!("No subcommand specified. Use --help for usage information.");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_ioregistry(client: &mut DiagnosticsRelayClient, matches: &ArgMatches) {
|
||||
let plane = matches.get_one::<String>("plane").map(|s| s.as_str());
|
||||
let name = matches.get_one::<String>("name").map(|s| s.as_str());
|
||||
let class = matches.get_one::<String>("class").map(|s| s.as_str());
|
||||
async fn handle_ioregistry(client: &mut DiagnosticsRelayClient, matches: &CollectedArguments) {
|
||||
let plane = matches.get_flag::<String>("plane");
|
||||
let name = matches.get_flag::<String>("name");
|
||||
let class = matches.get_flag::<String>("class");
|
||||
|
||||
let plane = plane.as_deref();
|
||||
let name = name.as_deref();
|
||||
let class = class.as_deref();
|
||||
|
||||
match client.ioregistry(plane, name, class).await {
|
||||
Ok(Some(data)) => {
|
||||
@@ -164,12 +134,14 @@ async fn handle_ioregistry(client: &mut DiagnosticsRelayClient, matches: &ArgMat
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_mobilegestalt(client: &mut DiagnosticsRelayClient, matches: &ArgMatches) {
|
||||
let keys = matches
|
||||
.get_many::<String>("keys")
|
||||
.map(|values| values.map(|s| s.to_string()).collect::<Vec<_>>());
|
||||
async fn handle_mobilegestalt(
|
||||
client: &mut DiagnosticsRelayClient,
|
||||
matches: &mut CollectedArguments,
|
||||
) {
|
||||
let keys = matches.next_argument::<String>().unwrap();
|
||||
let keys = keys.split(',').map(|x| x.to_string()).collect();
|
||||
|
||||
match client.mobilegestalt(keys).await {
|
||||
match client.mobilegestalt(Some(keys)).await {
|
||||
Ok(Some(data)) => {
|
||||
println!("{data:#?}");
|
||||
}
|
||||
|
||||
@@ -1,71 +1,18 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use futures_util::StreamExt;
|
||||
use idevice::{
|
||||
IdeviceService, RsdService, core_device::DiagnostisServiceClient,
|
||||
core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake,
|
||||
core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider, rsd::RsdHandshake,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkCommand};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
mod common;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new().help("Retrieve a sysdiagnose")
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("remotexpc")
|
||||
.about("Gets a sysdiagnose")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("tunneld")
|
||||
.long("tunneld")
|
||||
.help("Use tunneld")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("debug_proxy - connect to the debug proxy and run commands");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
let host = matches.get_one::<String>("host");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "diagnosticsservice-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(_arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let proxy = CoreDeviceProxy::connect(&*provider)
|
||||
.await
|
||||
.expect("no core proxy");
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use idevice::dvt::message::Message;
|
||||
use idevice::{dvt::message::Message, provider::IdeviceProvider};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let file = std::env::args().nth(1).expect("No file passed");
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Parse a DVT packet from a file")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path the the packet file"),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, _provider: Box<dyn IdeviceProvider>) {
|
||||
let mut arguments = arguments.clone();
|
||||
|
||||
let file: String = arguments.next_argument().expect("No file passed");
|
||||
let mut bytes = tokio::fs::File::open(file).await.unwrap();
|
||||
|
||||
let message = Message::from_reader(&mut bytes).await.unwrap();
|
||||
|
||||
@@ -1,60 +1,14 @@
|
||||
// Jackson Coxson
|
||||
// Heartbeat client
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{IdeviceService, heartbeat::HeartbeatClient};
|
||||
use idevice::{IdeviceService, heartbeat::HeartbeatClient, provider::IdeviceProvider};
|
||||
use jkcli::{CollectedArguments, JkCommand};
|
||||
|
||||
mod common;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new().help("heartbeat a device")
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
let matches = Command::new("core_device_proxy_tun")
|
||||
.about("Start a tunnel")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("heartbeat_client - heartbeat a device");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "heartbeat_client-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(_arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut heartbeat_client = HeartbeatClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Unable to connect to heartbeat");
|
||||
|
||||
@@ -1,64 +1,14 @@
|
||||
// Jackson Coxson
|
||||
// idevice Rust implementation of libimobiledevice's ideviceinfo
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{IdeviceService, lockdown::LockdownClient};
|
||||
use idevice::{IdeviceService, lockdown::LockdownClient, provider::IdeviceProvider};
|
||||
use jkcli::{CollectedArguments, JkCommand};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("core_device_proxy_tun")
|
||||
.about("Start a tunnel")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!(
|
||||
"ideviceinfo - get information from the idevice. Reimplementation of libimobiledevice's binary."
|
||||
);
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "ideviceinfo-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new().help("ideviceinfo - get information from the idevice. Reimplementation of libimobiledevice's binary.")
|
||||
}
|
||||
|
||||
pub async fn main(_arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut lockdown_client = match LockdownClient::connect(&*provider).await {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
|
||||
@@ -1,103 +1,73 @@
|
||||
// A minimal ideviceinstaller-like CLI to install/upgrade apps
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use idevice::utils::installation;
|
||||
use idevice::{provider::IdeviceProvider, utils::installation};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
mod common;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Manage files in the AFC jail of a device")
|
||||
.with_subcommand(
|
||||
"install",
|
||||
JkCommand::new()
|
||||
.help("Install a local .ipa or directory")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path to the .ipa or directory containing the app"),
|
||||
),
|
||||
)
|
||||
.with_subcommand(
|
||||
"upgrade",
|
||||
JkCommand::new()
|
||||
.help("Install a local .ipa or directory")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path to the .ipa or directory containing the app"),
|
||||
),
|
||||
)
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().expect("no sub arg");
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
let matches = Command::new("ideviceinstaller")
|
||||
.about("Install/upgrade apps on an iOS device (AFC + InstallationProxy)")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("install")
|
||||
.about("Install a local .ipa or directory")
|
||||
.arg(Arg::new("path").required(true).value_name("PATH")),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("upgrade")
|
||||
.about("Upgrade from a local .ipa or directory")
|
||||
.arg(Arg::new("path").required(true).value_name("PATH")),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("ideviceinstaller - install/upgrade apps using AFC + InstallationProxy (Rust)");
|
||||
println!("Copyright (c) 2025");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "ideviceinstaller").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
match sub_name.as_str() {
|
||||
"install" => {
|
||||
let path: String = sub_args.next_argument().expect("required");
|
||||
match installation::install_package_with_callback(
|
||||
&*provider,
|
||||
path,
|
||||
None,
|
||||
|(percentage, _)| async move {
|
||||
println!("Installing: {percentage}%");
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => println!("install success"),
|
||||
Err(e) => eprintln!("Install failed: {e}"),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("install") {
|
||||
let path: &String = matches.get_one("path").expect("required");
|
||||
match installation::install_package_with_callback(
|
||||
&*provider,
|
||||
path,
|
||||
None,
|
||||
|(percentage, _)| async move {
|
||||
println!("Installing: {percentage}%");
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => println!("install success"),
|
||||
Err(e) => eprintln!("Install failed: {e}"),
|
||||
"upgrade" => {
|
||||
let path: String = sub_args.next_argument().expect("required");
|
||||
match installation::upgrade_package_with_callback(
|
||||
&*provider,
|
||||
path,
|
||||
None,
|
||||
|(percentage, _)| async move {
|
||||
println!("Upgrading: {percentage}%");
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => println!("upgrade success"),
|
||||
Err(e) => eprintln!("Upgrade failed: {e}"),
|
||||
}
|
||||
}
|
||||
} else if let Some(matches) = matches.subcommand_matches("upgrade") {
|
||||
let path: &String = matches.get_one("path").expect("required");
|
||||
match installation::upgrade_package_with_callback(
|
||||
&*provider,
|
||||
path,
|
||||
None,
|
||||
|(percentage, _)| async move {
|
||||
println!("Upgrading: {percentage}%");
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => println!("upgrade success"),
|
||||
Err(e) => eprintln!("Upgrade failed: {e}"),
|
||||
}
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1,39 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy,
|
||||
installcoordination_proxy::InstallcoordinationProxy, rsd::RsdHandshake,
|
||||
installcoordination_proxy::InstallcoordinationProxy, provider::IdeviceProvider,
|
||||
rsd::RsdHandshake,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("installationcoordination_proxy")
|
||||
.about("")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("tunneld")
|
||||
.long("tunneld")
|
||||
.help("Use tunneld")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("info")
|
||||
.about("Get info about an app on the device")
|
||||
.arg(
|
||||
Arg::new("bundle_id")
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Interact with the RemoteXPC installation coordination proxy")
|
||||
.with_subcommand(
|
||||
"info",
|
||||
JkCommand::new()
|
||||
.help("Get info about an app on the device")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.help("The bundle ID to query"),
|
||||
.with_help("The bundle ID to query"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("uninstall")
|
||||
.about("Get info about an app on the device")
|
||||
.arg(
|
||||
Arg::new("bundle_id")
|
||||
.with_subcommand(
|
||||
"uninstall",
|
||||
JkCommand::new()
|
||||
.help("Uninstalls an app on the device")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.help("The bundle ID to query"),
|
||||
.with_help("The bundle ID to delete"),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("debug_proxy - connect to the debug proxy and run commands");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
let host = matches.get_one::<String>("host");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "app_service-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let proxy = CoreDeviceProxy::connect(&*provider)
|
||||
.await
|
||||
.expect("no core proxy");
|
||||
@@ -103,30 +55,38 @@ async fn main() {
|
||||
.await
|
||||
.expect("no connect");
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("info") {
|
||||
let bundle_id: &String = match matches.get_one("bundle_id") {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().unwrap();
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
let res = icp.query_app_path(bundle_id).await.expect("no info");
|
||||
println!("Path: {res}");
|
||||
} else if let Some(matches) = matches.subcommand_matches("uninstall") {
|
||||
let bundle_id: &String = match matches.get_one("bundle_id") {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match sub_name.as_str() {
|
||||
"info" => {
|
||||
let bundle_id: String = match sub_args.next_argument() {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
icp.uninstall_app(bundle_id)
|
||||
.await
|
||||
.expect("uninstall failed");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
let res = icp
|
||||
.query_app_path(bundle_id.as_str())
|
||||
.await
|
||||
.expect("no info");
|
||||
println!("Path: {res}");
|
||||
}
|
||||
"uninstall" => {
|
||||
let bundle_id: String = match sub_args.next_argument() {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
eprintln!("No bundle ID passed");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
icp.uninstall_app(bundle_id.as_str())
|
||||
.await
|
||||
.expect("uninstall failed");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,108 +1,84 @@
|
||||
// Jackson Coxson
|
||||
// Just lists apps for now
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{IdeviceService, installation_proxy::InstallationProxyClient};
|
||||
use idevice::{
|
||||
IdeviceService, installation_proxy::InstallationProxyClient, provider::IdeviceProvider,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("core_device_proxy_tun")
|
||||
.about("Start a tunnel")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Manage files in the AFC jail of a device")
|
||||
.with_subcommand(
|
||||
"lookup",
|
||||
JkCommand::new().help("Gets the apps on the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
.with_subcommand(
|
||||
"browse",
|
||||
JkCommand::new().help("Browses the apps on the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
.with_subcommand(
|
||||
"check_capabilities",
|
||||
JkCommand::new().help("Check the capabilities"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
.with_subcommand(
|
||||
"install",
|
||||
JkCommand::new()
|
||||
.help("Install an app in the AFC jail")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Path in the AFC jail"),
|
||||
),
|
||||
)
|
||||
.subcommand(Command::new("lookup").about("Gets the apps on the device"))
|
||||
.subcommand(Command::new("browse").about("Browses the apps on the device"))
|
||||
.subcommand(Command::new("check_capabilities").about("Check the capabilities"))
|
||||
.subcommand(
|
||||
Command::new("install")
|
||||
.about("Install an app in the AFC jail")
|
||||
.arg(Arg::new("path")),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!(
|
||||
"instproxy - query and manage apps installed on a device. Reimplementation of libimobiledevice's binary."
|
||||
);
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "instproxy-jkcoxson").await
|
||||
{
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut instproxy_client = InstallationProxyClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Unable to connect to instproxy");
|
||||
if matches.subcommand_matches("lookup").is_some() {
|
||||
let apps = instproxy_client.get_apps(Some("User"), None).await.unwrap();
|
||||
for app in apps.keys() {
|
||||
println!("{app}");
|
||||
}
|
||||
} else if matches.subcommand_matches("browse").is_some() {
|
||||
instproxy_client.browse(None).await.expect("browse failed");
|
||||
} else if matches.subcommand_matches("check_capabilities").is_some() {
|
||||
instproxy_client
|
||||
.check_capabilities_match(Vec::new(), None)
|
||||
.await
|
||||
.expect("check failed");
|
||||
} else if let Some(matches) = matches.subcommand_matches("install") {
|
||||
let path: &String = match matches.get_one("path") {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
eprintln!("No path passed, pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
instproxy_client
|
||||
.install_with_callback(
|
||||
path,
|
||||
None,
|
||||
async |(percentage, _)| {
|
||||
println!("Installing: {percentage}");
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to install")
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().expect("no sub arg");
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
match sub_name.as_str() {
|
||||
"lookup" => {
|
||||
let apps = instproxy_client.get_apps(Some("User"), None).await.unwrap();
|
||||
for app in apps.keys() {
|
||||
println!("{app}");
|
||||
}
|
||||
}
|
||||
"browse" => {
|
||||
instproxy_client.browse(None).await.expect("browse failed");
|
||||
}
|
||||
"check_capabilities" => {
|
||||
instproxy_client
|
||||
.check_capabilities_match(Vec::new(), None)
|
||||
.await
|
||||
.expect("check failed");
|
||||
}
|
||||
"install" => {
|
||||
let path: String = match sub_args.next_argument() {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
eprintln!("No path passed, pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
instproxy_client
|
||||
.install_with_callback(
|
||||
path,
|
||||
None,
|
||||
async |(percentage, _)| {
|
||||
println!("Installing: {percentage}");
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to install")
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,33 @@
|
||||
// Jackson Coxson
|
||||
// Just lists apps for now
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::provider::IdeviceProvider;
|
||||
use idevice::{IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake};
|
||||
|
||||
use idevice::dvt::location_simulation::LocationSimulationClient;
|
||||
use idevice::services::simulate_location::LocationSimulationService;
|
||||
mod common;
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Simulate device location")
|
||||
.with_subcommand(
|
||||
"clear",
|
||||
JkCommand::new().help("Clears the location set on the device"),
|
||||
)
|
||||
.with_subcommand(
|
||||
"set",
|
||||
JkCommand::new()
|
||||
.help("Set the location on the device")
|
||||
.with_argument(JkArgument::new().with_help("latitude").required(true))
|
||||
.with_argument(JkArgument::new().with_help("longitutde").required(true)),
|
||||
)
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
let matches = Command::new("simulate_location")
|
||||
.about("Simulate device location")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(Command::new("clear").about("Clears the location set on the device"))
|
||||
.subcommand(
|
||||
Command::new("set")
|
||||
.about("Set the location on the device")
|
||||
.arg(Arg::new("latitude").required(true))
|
||||
.arg(Arg::new("longitude").required(true)),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("simulate_location - Sets the simulated location on an iOS device");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "simulate_location-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().expect("No sub arg passed");
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
if let Ok(proxy) = CoreDeviceProxy::connect(&*provider).await {
|
||||
let rsd_port = proxy.handshake.server_rsd_port;
|
||||
@@ -86,42 +49,44 @@ async fn main() {
|
||||
let mut ls_client = LocationSimulationClient::new(&mut ls_client)
|
||||
.await
|
||||
.expect("Unable to get channel for location simulation");
|
||||
if matches.subcommand_matches("clear").is_some() {
|
||||
ls_client.clear().await.expect("Unable to clear");
|
||||
println!("Location cleared!");
|
||||
} else if let Some(matches) = matches.subcommand_matches("set") {
|
||||
let latitude: &String = match matches.get_one("latitude") {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
eprintln!("No latitude passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let latitude: f64 = latitude.parse().expect("Failed to parse as float");
|
||||
let longitude: &String = match matches.get_one("longitude") {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
eprintln!("No longitude passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let longitude: f64 = longitude.parse().expect("Failed to parse as float");
|
||||
ls_client
|
||||
.set(latitude, longitude)
|
||||
.await
|
||||
.expect("Failed to set location");
|
||||
|
||||
println!("Location set!");
|
||||
println!("Press ctrl-c to stop");
|
||||
loop {
|
||||
match sub_name.as_str() {
|
||||
"clear" => {
|
||||
ls_client.clear().await.expect("Unable to clear");
|
||||
println!("Location cleared!");
|
||||
}
|
||||
"set" => {
|
||||
let latitude: String = match sub_args.next_argument() {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
eprintln!("No latitude passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let latitude: f64 = latitude.parse().expect("Failed to parse as float");
|
||||
let longitude: String = match sub_args.next_argument() {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
eprintln!("No longitude passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let longitude: f64 = longitude.parse().expect("Failed to parse as float");
|
||||
ls_client
|
||||
.set(latitude, longitude)
|
||||
.await
|
||||
.expect("Failed to set location");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
|
||||
println!("Location set!");
|
||||
println!("Press ctrl-c to stop");
|
||||
loop {
|
||||
ls_client
|
||||
.set(latitude, longitude)
|
||||
.await
|
||||
.expect("Failed to set location");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
let mut location_client = match LocationSimulationService::connect(&*provider).await {
|
||||
@@ -133,35 +98,36 @@ async fn main() {
|
||||
return;
|
||||
}
|
||||
};
|
||||
if matches.subcommand_matches("clear").is_some() {
|
||||
location_client.clear().await.expect("Unable to clear");
|
||||
println!("Location cleared!");
|
||||
} else if let Some(matches) = matches.subcommand_matches("set") {
|
||||
let latitude: &String = match matches.get_one("latitude") {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
eprintln!("No latitude passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let longitude: &String = match matches.get_one("longitude") {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
eprintln!("No longitude passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
location_client
|
||||
.set(latitude, longitude)
|
||||
.await
|
||||
.expect("Failed to set location");
|
||||
match sub_name.as_str() {
|
||||
"clear" => {
|
||||
location_client.clear().await.expect("Unable to clear");
|
||||
println!("Location cleared!");
|
||||
}
|
||||
"set" => {
|
||||
let latitude: String = match sub_args.next_argument() {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
eprintln!("No latitude passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!("Location set!");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
let longitude: String = match sub_args.next_argument() {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
eprintln!("No longitude passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
location_client
|
||||
.set(latitude.as_str(), longitude.as_str())
|
||||
.await
|
||||
.expect("Failed to set location");
|
||||
|
||||
println!("Location set!");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,92 +1,79 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command, arg};
|
||||
use idevice::{IdeviceService, lockdown::LockdownClient, pretty_print_plist};
|
||||
use idevice::{IdeviceService, lockdown::LockdownClient, provider::IdeviceProvider};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand, JkFlag};
|
||||
use plist::Value;
|
||||
use plist_macro::pretty_print_plist;
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("lockdown")
|
||||
.about("Start a tunnel")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Interact with lockdown")
|
||||
.with_subcommand(
|
||||
"get",
|
||||
JkCommand::new()
|
||||
.help("Gets a value from lockdown")
|
||||
.with_argument(JkArgument::new().with_help("The value to get")),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
.with_subcommand(
|
||||
"set",
|
||||
JkCommand::new()
|
||||
.help("Gets a value from lockdown")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("The value to set")
|
||||
.required(true),
|
||||
)
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("The value key to set")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
.with_subcommand(
|
||||
"recovery",
|
||||
JkCommand::new().help("Tell the device to enter recovery mode"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
.with_flag(
|
||||
JkFlag::new("domain")
|
||||
.with_help("The domain to set/get in")
|
||||
.with_argument(JkArgument::new().required(true)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("get")
|
||||
.about("Gets a value")
|
||||
.arg(arg!(-v --value <STRING> "the value to get").required(false))
|
||||
.arg(arg!(-d --domain <STRING> "the domain to get in").required(false)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("set")
|
||||
.about("Sets a lockdown value")
|
||||
.arg(arg!(-k --key <STRING> "the key to set").required(true))
|
||||
.arg(arg!(-v --value <STRING> "the value to set the key to").required(true))
|
||||
.arg(arg!(-d --domain <STRING> "the domain to get in").required(false)),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!(
|
||||
"lockdown - query and manage values on a device. Reimplementation of libimobiledevice's binary."
|
||||
);
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "ideviceinfo-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
.with_flag(JkFlag::new("no-session").with_help("Don't start a TLS session"))
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut lockdown_client = LockdownClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Unable to connect to lockdown");
|
||||
|
||||
lockdown_client
|
||||
.start_session(&provider.get_pairing_file().await.expect("no pairing file"))
|
||||
.await
|
||||
.expect("no session");
|
||||
if !arguments.has_flag("no-session") {
|
||||
lockdown_client
|
||||
.start_session(&provider.get_pairing_file().await.expect("no pairing file"))
|
||||
.await
|
||||
.expect("no session");
|
||||
}
|
||||
|
||||
match matches.subcommand() {
|
||||
Some(("get", sub_m)) => {
|
||||
let key = sub_m.get_one::<String>("value").map(|x| x.as_str());
|
||||
let domain = sub_m.get_one::<String>("domain").map(|x| x.as_str());
|
||||
let domain: Option<String> = arguments.get_flag("domain");
|
||||
let domain = domain.as_deref();
|
||||
|
||||
match lockdown_client.get_value(key, domain).await {
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().expect("No subcommand");
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
match sub_name.as_str() {
|
||||
"get" => {
|
||||
let key: Option<String> = sub_args.next_argument();
|
||||
|
||||
match lockdown_client
|
||||
.get_value(
|
||||
match &key {
|
||||
Some(k) => Some(k.as_str()),
|
||||
None => None,
|
||||
},
|
||||
domain,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(value) => {
|
||||
println!("{}", pretty_print_plist(&value));
|
||||
}
|
||||
@@ -95,25 +82,21 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(("set", sub_m)) => {
|
||||
let key = sub_m.get_one::<String>("key").unwrap();
|
||||
let value_str = sub_m.get_one::<String>("value").unwrap();
|
||||
let domain = sub_m.get_one::<String>("domain");
|
||||
"set" => {
|
||||
let value_str: String = sub_args.next_argument().unwrap();
|
||||
let key: String = sub_args.next_argument().unwrap();
|
||||
|
||||
let value = Value::String(value_str.clone());
|
||||
|
||||
match lockdown_client
|
||||
.set_value(key, value, domain.map(|x| x.as_str()))
|
||||
.await
|
||||
{
|
||||
match lockdown_client.set_value(key, value, domain).await {
|
||||
Ok(()) => println!("Successfully set"),
|
||||
Err(e) => eprintln!("Error setting value: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
eprintln!("No subcommand provided. Try `--help` for usage.");
|
||||
}
|
||||
"recovery" => lockdown_client
|
||||
.enter_recovery()
|
||||
.await
|
||||
.expect("Failed to enter recovery"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
338
tools/src/main.rs
Normal file
338
tools/src/main.rs
Normal file
@@ -0,0 +1,338 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use idevice::{
|
||||
pairing_file::PairingFile,
|
||||
provider::{IdeviceProvider, TcpProvider},
|
||||
usbmuxd::{Connection, UsbmuxdAddr, UsbmuxdConnection, UsbmuxdDevice},
|
||||
};
|
||||
use jkcli::{JkArgument, JkCommand, JkFlag};
|
||||
|
||||
mod activation;
|
||||
mod afc;
|
||||
mod amfi;
|
||||
mod app_service;
|
||||
mod bt_packet_logger;
|
||||
mod companion_proxy;
|
||||
mod crash_logs;
|
||||
mod debug_proxy;
|
||||
mod diagnostics;
|
||||
mod diagnosticsservice;
|
||||
mod dvt_packet_parser;
|
||||
mod heartbeat_client;
|
||||
mod ideviceinfo;
|
||||
mod ideviceinstaller;
|
||||
mod installcoordination_proxy;
|
||||
mod instproxy;
|
||||
mod location_simulation;
|
||||
mod lockdown;
|
||||
mod misagent;
|
||||
mod mobilebackup2;
|
||||
mod mounter;
|
||||
mod notification_proxy_client;
|
||||
mod notifications;
|
||||
mod os_trace_relay;
|
||||
mod pair;
|
||||
mod pcapd;
|
||||
mod preboard;
|
||||
mod process_control;
|
||||
mod remotexpc;
|
||||
mod restore_service;
|
||||
mod screenshot;
|
||||
mod springboardservices;
|
||||
mod syslog_relay;
|
||||
|
||||
mod pcap;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Set the base CLI
|
||||
let arguments = JkCommand::new()
|
||||
.with_flag(
|
||||
JkFlag::new("about")
|
||||
.with_help("Prints the about message")
|
||||
.with_short_curcuit(|| {
|
||||
eprintln!("idevice-rs-tools - Jackson Coxson\n");
|
||||
eprintln!("Tools to manage and manipulate iOS devices");
|
||||
eprintln!("Version {}", env!("CARGO_PKG_VERSION"));
|
||||
eprintln!("https://github.com/jkcoxson/idevice");
|
||||
eprintln!("\nOn to eternal perfection!");
|
||||
std::process::exit(0);
|
||||
}),
|
||||
)
|
||||
.with_flag(
|
||||
JkFlag::new("version")
|
||||
.with_help("Prints the version")
|
||||
.with_short_curcuit(|| {
|
||||
println!("{}", env!("CARGO_PKG_VERSION"));
|
||||
std::process::exit(0);
|
||||
}),
|
||||
)
|
||||
.with_flag(
|
||||
JkFlag::new("pairing-file")
|
||||
.with_argument(JkArgument::new().required(true))
|
||||
.with_help("The path to the pairing file to use"),
|
||||
)
|
||||
.with_flag(
|
||||
JkFlag::new("host")
|
||||
.with_argument(JkArgument::new().required(true))
|
||||
.with_help("The host to connect to"),
|
||||
)
|
||||
.with_flag(
|
||||
JkFlag::new("udid")
|
||||
.with_argument(JkArgument::new().required(true))
|
||||
.with_help("The UDID to use"),
|
||||
)
|
||||
.with_subcommand("activation", activation::register())
|
||||
.with_subcommand("afc", afc::register())
|
||||
.with_subcommand("amfi", amfi::register())
|
||||
.with_subcommand("app_service", app_service::register())
|
||||
.with_subcommand("bt_packet_logger", bt_packet_logger::register())
|
||||
.with_subcommand("companion_proxy", companion_proxy::register())
|
||||
.with_subcommand("crash_logs", crash_logs::register())
|
||||
.with_subcommand("debug_proxy", debug_proxy::register())
|
||||
.with_subcommand("diagnostics", diagnostics::register())
|
||||
.with_subcommand("diagnosticsservice", diagnosticsservice::register())
|
||||
.with_subcommand("dvt_packet_parser", dvt_packet_parser::register())
|
||||
.with_subcommand("heartbeat_client", heartbeat_client::register())
|
||||
.with_subcommand("ideviceinfo", ideviceinfo::register())
|
||||
.with_subcommand("ideviceinstaller", ideviceinstaller::register())
|
||||
.with_subcommand(
|
||||
"installcoordination_proxy",
|
||||
installcoordination_proxy::register(),
|
||||
)
|
||||
.with_subcommand("instproxy", instproxy::register())
|
||||
.with_subcommand("location_simulation", location_simulation::register())
|
||||
.with_subcommand("lockdown", lockdown::register())
|
||||
.with_subcommand("misagent", misagent::register())
|
||||
.with_subcommand("mobilebackup2", mobilebackup2::register())
|
||||
.with_subcommand("mounter", mounter::register())
|
||||
.with_subcommand("notifications", notifications::register())
|
||||
.with_subcommand("notification_proxy", notification_proxy_client::register())
|
||||
.with_subcommand("os_trace_relay", os_trace_relay::register())
|
||||
.with_subcommand("pair", pair::register())
|
||||
.with_subcommand("pcapd", pcapd::register())
|
||||
.with_subcommand("preboard", preboard::register())
|
||||
.with_subcommand("process_control", process_control::register())
|
||||
.with_subcommand("remotexpc", remotexpc::register())
|
||||
.with_subcommand("restore_service", restore_service::register())
|
||||
.with_subcommand("screenshot", screenshot::register())
|
||||
.with_subcommand("springboard", springboardservices::register())
|
||||
.with_subcommand("syslog_relay", syslog_relay::register())
|
||||
.subcommand_required(true)
|
||||
.collect()
|
||||
.expect("Failed to collect CLI args");
|
||||
|
||||
let udid = arguments.get_flag::<String>("udid");
|
||||
let host = arguments.get_flag::<String>("host");
|
||||
let pairing_file = arguments.get_flag::<String>("pairing-file");
|
||||
|
||||
let provider = match get_provider(udid, host, pairing_file, "idevice-rs-tools").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let (subcommand, sub_args) = match arguments.first_subcommand() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
eprintln!("No subcommand passed, pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match subcommand.as_str() {
|
||||
"activation" => {
|
||||
activation::main(sub_args, provider).await;
|
||||
}
|
||||
"afc" => {
|
||||
afc::main(sub_args, provider).await;
|
||||
}
|
||||
"amfi" => {
|
||||
amfi::main(sub_args, provider).await;
|
||||
}
|
||||
"app_service" => {
|
||||
app_service::main(sub_args, provider).await;
|
||||
}
|
||||
"bt_packet_logger" => {
|
||||
bt_packet_logger::main(sub_args, provider).await;
|
||||
}
|
||||
"companion_proxy" => {
|
||||
companion_proxy::main(sub_args, provider).await;
|
||||
}
|
||||
"crash_logs" => {
|
||||
crash_logs::main(sub_args, provider).await;
|
||||
}
|
||||
"debug_proxy" => {
|
||||
debug_proxy::main(sub_args, provider).await;
|
||||
}
|
||||
"diagnostics" => {
|
||||
diagnostics::main(sub_args, provider).await;
|
||||
}
|
||||
"diagnosticsservice" => {
|
||||
diagnosticsservice::main(sub_args, provider).await;
|
||||
}
|
||||
"dvt_packet_parser" => {
|
||||
dvt_packet_parser::main(sub_args, provider).await;
|
||||
}
|
||||
"heartbeat_client" => {
|
||||
heartbeat_client::main(sub_args, provider).await;
|
||||
}
|
||||
"ideviceinfo" => {
|
||||
ideviceinfo::main(sub_args, provider).await;
|
||||
}
|
||||
"ideviceinstaller" => {
|
||||
ideviceinstaller::main(sub_args, provider).await;
|
||||
}
|
||||
"installcoordination_proxy" => {
|
||||
installcoordination_proxy::main(sub_args, provider).await;
|
||||
}
|
||||
"instproxy" => {
|
||||
instproxy::main(sub_args, provider).await;
|
||||
}
|
||||
"location_simulation" => {
|
||||
location_simulation::main(sub_args, provider).await;
|
||||
}
|
||||
"lockdown" => {
|
||||
lockdown::main(sub_args, provider).await;
|
||||
}
|
||||
"misagent" => {
|
||||
misagent::main(sub_args, provider).await;
|
||||
}
|
||||
"mobilebackup2" => {
|
||||
mobilebackup2::main(sub_args, provider).await;
|
||||
}
|
||||
"mounter" => {
|
||||
mounter::main(sub_args, provider).await;
|
||||
}
|
||||
"notifications" => {
|
||||
notifications::main(sub_args, provider).await;
|
||||
}
|
||||
"notification_proxy" => {
|
||||
notification_proxy_client::main(sub_args, provider).await;
|
||||
}
|
||||
"os_trace_relay" => {
|
||||
os_trace_relay::main(sub_args, provider).await;
|
||||
}
|
||||
"pair" => {
|
||||
pair::main(sub_args, provider).await;
|
||||
}
|
||||
"pcapd" => {
|
||||
pcapd::main(sub_args, provider).await;
|
||||
}
|
||||
"preboard" => {
|
||||
preboard::main(sub_args, provider).await;
|
||||
}
|
||||
"process_control" => {
|
||||
process_control::main(sub_args, provider).await;
|
||||
}
|
||||
"remotexpc" => {
|
||||
remotexpc::main(sub_args, provider).await;
|
||||
}
|
||||
"restore_service" => {
|
||||
restore_service::main(sub_args, provider).await;
|
||||
}
|
||||
"screenshot" => {
|
||||
screenshot::main(sub_args, provider).await;
|
||||
}
|
||||
"springboard" => {
|
||||
springboardservices::main(sub_args, provider).await;
|
||||
}
|
||||
"syslog_relay" => {
|
||||
syslog_relay::main(sub_args, provider).await;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_provider(
|
||||
udid: Option<String>,
|
||||
host: Option<String>,
|
||||
pairing_file: Option<String>,
|
||||
label: &str,
|
||||
) -> Result<Box<dyn IdeviceProvider>, String> {
|
||||
let provider: Box<dyn IdeviceProvider> = if let Some(udid) = udid {
|
||||
let mut usbmuxd = if let Ok(var) = std::env::var("USBMUXD_SOCKET_ADDRESS") {
|
||||
let socket = SocketAddr::from_str(&var).expect("Bad USBMUXD_SOCKET_ADDRESS");
|
||||
let socket = tokio::net::TcpStream::connect(socket)
|
||||
.await
|
||||
.expect("unable to connect to socket address");
|
||||
UsbmuxdConnection::new(Box::new(socket), 1)
|
||||
} else {
|
||||
UsbmuxdConnection::default()
|
||||
.await
|
||||
.expect("Unable to connect to usbmxud")
|
||||
};
|
||||
|
||||
let dev = match usbmuxd.get_device(udid.as_str()).await {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
return Err(format!("Device not found: {e:?}"));
|
||||
}
|
||||
};
|
||||
Box::new(dev.to_provider(UsbmuxdAddr::from_env_var().unwrap(), label))
|
||||
} else if let Some(host) = host
|
||||
&& let Some(pairing_file) = pairing_file
|
||||
{
|
||||
let host = match IpAddr::from_str(host.as_str()) {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
return Err(format!("Invalid host: {e:?}"));
|
||||
}
|
||||
};
|
||||
let pairing_file = match PairingFile::read_from_file(pairing_file) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
return Err(format!("Unable to read pairing file: {e:?}"));
|
||||
}
|
||||
};
|
||||
|
||||
Box::new(TcpProvider {
|
||||
addr: host,
|
||||
pairing_file,
|
||||
label: label.to_string(),
|
||||
})
|
||||
} else {
|
||||
let mut usbmuxd = if let Ok(var) = std::env::var("USBMUXD_SOCKET_ADDRESS") {
|
||||
let socket = SocketAddr::from_str(&var).expect("Bad USBMUXD_SOCKET_ADDRESS");
|
||||
let socket = tokio::net::TcpStream::connect(socket)
|
||||
.await
|
||||
.expect("unable to connect to socket address");
|
||||
UsbmuxdConnection::new(Box::new(socket), 1)
|
||||
} else {
|
||||
UsbmuxdConnection::default()
|
||||
.await
|
||||
.expect("Unable to connect to usbmxud")
|
||||
};
|
||||
let devs = match usbmuxd.get_devices().await {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
return Err(format!("Unable to get devices from usbmuxd: {e:?}"));
|
||||
}
|
||||
};
|
||||
let usb_devs: Vec<&UsbmuxdDevice> = devs
|
||||
.iter()
|
||||
.filter(|x| x.connection_type == Connection::Usb)
|
||||
.collect();
|
||||
|
||||
if devs.is_empty() {
|
||||
return Err("No devices connected!".to_string());
|
||||
}
|
||||
|
||||
let chosen_dev = if !usb_devs.is_empty() {
|
||||
usb_devs[0]
|
||||
} else {
|
||||
&devs[0]
|
||||
};
|
||||
Box::new(chosen_dev.to_provider(UsbmuxdAddr::from_env_var().unwrap(), label))
|
||||
};
|
||||
Ok(provider)
|
||||
}
|
||||
@@ -2,99 +2,89 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Arg, Command, arg, value_parser};
|
||||
use idevice::{IdeviceService, misagent::MisagentClient};
|
||||
use idevice::{IdeviceService, misagent::MisagentClient, provider::IdeviceProvider};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("core_device_proxy_tun")
|
||||
.about("Start a tunnel")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("list")
|
||||
.about("Lists the images mounted on the device")
|
||||
.arg(
|
||||
arg!(-s --save <FOLDER> "the folder to save the profiles to")
|
||||
.value_parser(value_parser!(PathBuf)),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Manage provisioning profiles on the device")
|
||||
.with_subcommand(
|
||||
"list",
|
||||
JkCommand::new()
|
||||
.help("List profiles installed on the device")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Path to save profiles from the device")
|
||||
.required(false),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("remove")
|
||||
.about("Remove a provisioning profile")
|
||||
.arg(Arg::new("id").required(true).index(1)),
|
||||
.with_subcommand(
|
||||
"remove",
|
||||
JkCommand::new()
|
||||
.help("Remove a profile installed on the device")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("ID of the profile to remove")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
.with_subcommand(
|
||||
"install",
|
||||
JkCommand::new()
|
||||
.help("Install a provisioning profile on the device")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Path to the provisioning profile to install")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!(
|
||||
"mounter - query and manage images mounted on a device. Reimplementation of libimobiledevice's binary."
|
||||
);
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "misagent-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut misagent_client = MisagentClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Unable to connect to misagent");
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("list") {
|
||||
let images = misagent_client
|
||||
.copy_all()
|
||||
.await
|
||||
.expect("Unable to get images");
|
||||
if let Some(path) = matches.get_one::<PathBuf>("save") {
|
||||
tokio::fs::create_dir_all(path)
|
||||
.await
|
||||
.expect("Unable to create save DIR");
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().expect("No subcommand passed");
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
for (index, image) in images.iter().enumerate() {
|
||||
let f = path.join(format!("{index}.pem"));
|
||||
tokio::fs::write(f, image)
|
||||
match sub_name.as_str() {
|
||||
"list" => {
|
||||
let images = misagent_client
|
||||
.copy_all()
|
||||
.await
|
||||
.expect("Unable to get images");
|
||||
if let Some(path) = sub_args.next_argument::<PathBuf>() {
|
||||
tokio::fs::create_dir_all(&path)
|
||||
.await
|
||||
.expect("Failed to write image");
|
||||
.expect("Unable to create save DIR");
|
||||
|
||||
for (index, image) in images.iter().enumerate() {
|
||||
let f = path.join(format!("{index}.pem"));
|
||||
tokio::fs::write(f, image)
|
||||
.await
|
||||
.expect("Failed to write image");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(matches) = matches.subcommand_matches("remove") {
|
||||
let id = matches.get_one::<String>("id").expect("No ID passed");
|
||||
misagent_client.remove(id).await.expect("Failed to remove");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
"remove" => {
|
||||
let id = sub_args.next_argument::<String>().expect("No ID passed");
|
||||
misagent_client
|
||||
.remove(id.as_str())
|
||||
.await
|
||||
.expect("Failed to remove");
|
||||
}
|
||||
"install" => {
|
||||
let path = sub_args
|
||||
.next_argument::<PathBuf>()
|
||||
.expect("No profile path passed");
|
||||
let profile = tokio::fs::read(path).await.expect("Unable to read profile");
|
||||
misagent_client
|
||||
.install(profile)
|
||||
.await
|
||||
.expect("Failed to install profile");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,191 +1,135 @@
|
||||
// Jackson Coxson
|
||||
// Mobile Backup 2 tool for iOS devices
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService,
|
||||
mobilebackup2::{MobileBackup2Client, RestoreOptions},
|
||||
provider::IdeviceProvider,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand, JkFlag};
|
||||
use plist::Dictionary;
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("mobilebackup2")
|
||||
.about("Mobile Backup 2 tool for iOS devices")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("info")
|
||||
.about("Get backup information from a local backup directory")
|
||||
.arg(Arg::new("dir").long("dir").value_name("DIR").required(true))
|
||||
.arg(
|
||||
Arg::new("source")
|
||||
.long("source")
|
||||
.value_name("SOURCE")
|
||||
.help("Source identifier (defaults to current UDID)"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("list")
|
||||
.about("List files of the last backup from a local backup directory")
|
||||
.arg(Arg::new("dir").long("dir").value_name("DIR").required(true))
|
||||
.arg(Arg::new("source").long("source").value_name("SOURCE")),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("backup")
|
||||
.about("Start a backup operation")
|
||||
.arg(
|
||||
Arg::new("dir")
|
||||
.long("dir")
|
||||
.value_name("DIR")
|
||||
.help("Backup directory on host")
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Mobile Backup 2 tool for iOS devices")
|
||||
.with_subcommand(
|
||||
"info",
|
||||
JkCommand::new()
|
||||
.help("Get backup information from a local backup directory")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Backup DIR to read from")
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("target")
|
||||
.long("target")
|
||||
.value_name("TARGET")
|
||||
.help("Target identifier for the backup"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("source")
|
||||
.long("source")
|
||||
.value_name("SOURCE")
|
||||
.help("Source identifier for the backup"),
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Source identifier (defaults to current UDID)")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("restore")
|
||||
.about("Restore from a local backup directory (DeviceLink)")
|
||||
.arg(Arg::new("dir").long("dir").value_name("DIR").required(true))
|
||||
.arg(
|
||||
Arg::new("source")
|
||||
.long("source")
|
||||
.value_name("SOURCE")
|
||||
.help("Source UDID; defaults to current device UDID"),
|
||||
.with_subcommand(
|
||||
"list",
|
||||
JkCommand::new()
|
||||
.help("List files of the last backup from a local backup directory")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Backup DIR to read from")
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("password")
|
||||
.long("password")
|
||||
.value_name("PWD")
|
||||
.help("Backup password if encrypted"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no-reboot")
|
||||
.long("no-reboot")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no-copy")
|
||||
.long("no-copy")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no-settings")
|
||||
.long("no-settings")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("system")
|
||||
.long("system")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("remove")
|
||||
.long("remove")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Source identifier (defaults to current UDID)")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("unback")
|
||||
.about("Unpack a complete backup to device hierarchy")
|
||||
.arg(Arg::new("dir").long("dir").value_name("DIR").required(true))
|
||||
.arg(Arg::new("source").long("source").value_name("SOURCE"))
|
||||
.arg(Arg::new("password").long("password").value_name("PWD")),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("extract")
|
||||
.about("Extract a file from a previous backup")
|
||||
.arg(Arg::new("dir").long("dir").value_name("DIR").required(true))
|
||||
.arg(Arg::new("source").long("source").value_name("SOURCE"))
|
||||
.arg(
|
||||
Arg::new("domain")
|
||||
.long("domain")
|
||||
.value_name("DOMAIN")
|
||||
.with_subcommand(
|
||||
"backup",
|
||||
JkCommand::new()
|
||||
.help("Start a backup operation")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Backup directory on host")
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("path")
|
||||
.long("path")
|
||||
.value_name("REL_PATH")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Target identifier for the backup")
|
||||
.required(true),
|
||||
)
|
||||
.arg(Arg::new("password").long("password").value_name("PWD")),
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Source identifier for the backup")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("change-password")
|
||||
.about("Change backup password")
|
||||
.arg(Arg::new("dir").long("dir").value_name("DIR").required(true))
|
||||
.arg(Arg::new("old").long("old").value_name("OLD"))
|
||||
.arg(Arg::new("new").long("new").value_name("NEW")),
|
||||
.with_subcommand(
|
||||
"restore",
|
||||
JkCommand::new()
|
||||
.help("Restore from a local backup directory (DeviceLink)")
|
||||
.with_argument(JkArgument::new().with_help("DIR").required(true))
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Source UDID; defaults to current device UDID")
|
||||
.required(true),
|
||||
)
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Backup password if encrypted")
|
||||
.required(true),
|
||||
)
|
||||
.with_flag(JkFlag::new("no-reboot"))
|
||||
.with_flag(JkFlag::new("no-copy"))
|
||||
.with_flag(JkFlag::new("no-settings"))
|
||||
.with_flag(JkFlag::new("system"))
|
||||
.with_flag(JkFlag::new("remove")),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("erase-device")
|
||||
.about("Erase the device via mobilebackup2")
|
||||
.arg(Arg::new("dir").long("dir").value_name("DIR").required(true)),
|
||||
.with_subcommand(
|
||||
"unback",
|
||||
JkCommand::new()
|
||||
.help("Unpack a complete backup to device hierarchy")
|
||||
.with_argument(JkArgument::new().with_help("DIR").required(true))
|
||||
.with_argument(JkArgument::new().with_help("Source"))
|
||||
.with_argument(JkArgument::new().with_help("Password")),
|
||||
)
|
||||
.subcommand(Command::new("freespace").about("Get free space information"))
|
||||
.subcommand(Command::new("encryption").about("Check backup encryption status"))
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("mobilebackup2 - manage device backups using Mobile Backup 2 service");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "mobilebackup2-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("Error creating provider: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
.with_subcommand(
|
||||
"extract",
|
||||
JkCommand::new()
|
||||
.help("Extract a file from a previous backup")
|
||||
.with_argument(JkArgument::new().with_help("DIR").required(true))
|
||||
.with_argument(JkArgument::new().with_help("Source").required(true))
|
||||
.with_argument(JkArgument::new().with_help("Domain").required(true))
|
||||
.with_argument(JkArgument::new().with_help("Path").required(true))
|
||||
.with_argument(JkArgument::new().with_help("Password").required(true)),
|
||||
)
|
||||
.with_subcommand(
|
||||
"change-password",
|
||||
JkCommand::new()
|
||||
.help("Change backup password")
|
||||
.with_argument(JkArgument::new().with_help("DIR").required(true))
|
||||
.with_argument(JkArgument::new().with_help("Old password").required(true))
|
||||
.with_argument(JkArgument::new().with_help("New password").required(true)),
|
||||
)
|
||||
.with_subcommand(
|
||||
"erase-device",
|
||||
JkCommand::new()
|
||||
.help("Erase the device via mobilebackup2")
|
||||
.with_argument(JkArgument::new().with_help("DIR").required(true)),
|
||||
)
|
||||
.with_subcommand(
|
||||
"freespace",
|
||||
JkCommand::new().help("Get free space information"),
|
||||
)
|
||||
.with_subcommand(
|
||||
"encryption",
|
||||
JkCommand::new().help("Check backup encryption status"),
|
||||
)
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut backup_client = match MobileBackup2Client::connect(&*provider).await {
|
||||
Ok(client) => client,
|
||||
Err(e) => {
|
||||
@@ -194,11 +138,16 @@ async fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
match matches.subcommand() {
|
||||
Some(("info", sub)) => {
|
||||
let dir = sub.get_one::<String>("dir").unwrap();
|
||||
let source = sub.get_one::<String>("source").map(|s| s.as_str());
|
||||
match backup_client.info_from_path(Path::new(dir), source).await {
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().unwrap();
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
match sub_name.as_str() {
|
||||
"info" => {
|
||||
let dir = sub_args.next_argument::<String>().unwrap();
|
||||
let source = sub_args.next_argument::<String>();
|
||||
let source = source.as_deref();
|
||||
|
||||
match backup_client.info_from_path(Path::new(&dir), source).await {
|
||||
Ok(dict) => {
|
||||
println!("Backup Information:");
|
||||
for (k, v) in dict {
|
||||
@@ -208,10 +157,12 @@ async fn main() {
|
||||
Err(e) => eprintln!("Failed to get info: {e}"),
|
||||
}
|
||||
}
|
||||
Some(("list", sub)) => {
|
||||
let dir = sub.get_one::<String>("dir").unwrap();
|
||||
let source = sub.get_one::<String>("source").map(|s| s.as_str());
|
||||
match backup_client.list_from_path(Path::new(dir), source).await {
|
||||
"list" => {
|
||||
let dir = sub_args.next_argument::<String>().unwrap();
|
||||
let source = sub_args.next_argument::<String>();
|
||||
let source = source.as_deref();
|
||||
|
||||
match backup_client.list_from_path(Path::new(&dir), source).await {
|
||||
Ok(dict) => {
|
||||
println!("List Response:");
|
||||
for (k, v) in dict {
|
||||
@@ -221,12 +172,12 @@ async fn main() {
|
||||
Err(e) => eprintln!("Failed to list: {e}"),
|
||||
}
|
||||
}
|
||||
Some(("backup", sub_matches)) => {
|
||||
let target = sub_matches.get_one::<String>("target").map(|s| s.as_str());
|
||||
let source = sub_matches.get_one::<String>("source").map(|s| s.as_str());
|
||||
let dir = sub_matches
|
||||
.get_one::<String>("dir")
|
||||
.expect("dir is required");
|
||||
"backup" => {
|
||||
let target = sub_args.next_argument::<String>();
|
||||
let target = target.as_deref();
|
||||
let source = sub_args.next_argument::<String>();
|
||||
let source = source.as_deref();
|
||||
let dir = sub_args.next_argument::<String>().expect("dir is required");
|
||||
|
||||
println!("Starting backup operation...");
|
||||
let res = backup_client
|
||||
@@ -234,95 +185,112 @@ async fn main() {
|
||||
.await;
|
||||
if let Err(e) = res {
|
||||
eprintln!("Failed to send backup request: {e}");
|
||||
} else if let Err(e) = process_dl_loop(&mut backup_client, Path::new(dir)).await {
|
||||
} else if let Err(e) = process_dl_loop(&mut backup_client, Path::new(&dir)).await {
|
||||
eprintln!("Backup failed during DL loop: {e}");
|
||||
} else {
|
||||
println!("Backup flow finished");
|
||||
}
|
||||
}
|
||||
Some(("restore", sub)) => {
|
||||
let dir = sub.get_one::<String>("dir").unwrap();
|
||||
let source = sub.get_one::<String>("source").map(|s| s.as_str());
|
||||
"restore" => {
|
||||
let dir = sub_args.next_argument::<String>().unwrap();
|
||||
let source = sub_args.next_argument::<String>();
|
||||
let source = source.as_deref();
|
||||
|
||||
let mut ropts = RestoreOptions::new();
|
||||
if sub.get_flag("no-reboot") {
|
||||
if sub_args.has_flag("no-reboot") {
|
||||
ropts = ropts.with_reboot(false);
|
||||
}
|
||||
if sub.get_flag("no-copy") {
|
||||
if sub_args.has_flag("no-copy") {
|
||||
ropts = ropts.with_copy(false);
|
||||
}
|
||||
if sub.get_flag("no-settings") {
|
||||
if sub_args.has_flag("no-settings") {
|
||||
ropts = ropts.with_preserve_settings(false);
|
||||
}
|
||||
if sub.get_flag("system") {
|
||||
if sub_args.has_flag("system") {
|
||||
ropts = ropts.with_system_files(true);
|
||||
}
|
||||
if sub.get_flag("remove") {
|
||||
if sub_args.has_flag("remove") {
|
||||
ropts = ropts.with_remove_items_not_restored(true);
|
||||
}
|
||||
if let Some(pw) = sub.get_one::<String>("password") {
|
||||
if let Some(pw) = sub_args.next_argument::<String>() {
|
||||
ropts = ropts.with_password(pw);
|
||||
}
|
||||
match backup_client
|
||||
.restore_from_path(Path::new(dir), source, Some(ropts))
|
||||
.restore_from_path(Path::new(&dir), source, Some(ropts))
|
||||
.await
|
||||
{
|
||||
Ok(_) => println!("Restore flow finished"),
|
||||
Err(e) => eprintln!("Restore failed: {e}"),
|
||||
}
|
||||
}
|
||||
Some(("unback", sub)) => {
|
||||
let dir = sub.get_one::<String>("dir").unwrap();
|
||||
let source = sub.get_one::<String>("source").map(|s| s.as_str());
|
||||
let password = sub.get_one::<String>("password").map(|s| s.as_str());
|
||||
"unback" => {
|
||||
let dir = sub_args.next_argument::<String>().unwrap();
|
||||
let source = sub_args.next_argument::<String>();
|
||||
let source = source.as_deref();
|
||||
let password = sub_args.next_argument::<String>();
|
||||
let password = password.as_deref();
|
||||
|
||||
match backup_client
|
||||
.unback_from_path(Path::new(dir), password, source)
|
||||
.unback_from_path(Path::new(&dir), password, source)
|
||||
.await
|
||||
{
|
||||
Ok(_) => println!("Unback finished"),
|
||||
Err(e) => eprintln!("Unback failed: {e}"),
|
||||
}
|
||||
}
|
||||
Some(("extract", sub)) => {
|
||||
let dir = sub.get_one::<String>("dir").unwrap();
|
||||
let source = sub.get_one::<String>("source").map(|s| s.as_str());
|
||||
let domain = sub.get_one::<String>("domain").unwrap();
|
||||
let rel = sub.get_one::<String>("path").unwrap();
|
||||
let password = sub.get_one::<String>("password").map(|s| s.as_str());
|
||||
"extract" => {
|
||||
let dir = sub_args.next_argument::<String>().unwrap();
|
||||
let source = sub_args.next_argument::<String>();
|
||||
let source = source.as_deref();
|
||||
let domain = sub_args.next_argument::<String>().unwrap();
|
||||
let rel = sub_args.next_argument::<String>().unwrap();
|
||||
let password = sub_args.next_argument::<String>();
|
||||
let password = password.as_deref();
|
||||
|
||||
match backup_client
|
||||
.extract_from_path(domain, rel, Path::new(dir), password, source)
|
||||
.extract_from_path(
|
||||
domain.as_str(),
|
||||
rel.as_str(),
|
||||
Path::new(&dir),
|
||||
password,
|
||||
source,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => println!("Extract finished"),
|
||||
Err(e) => eprintln!("Extract failed: {e}"),
|
||||
}
|
||||
}
|
||||
Some(("change-password", sub)) => {
|
||||
let dir = sub.get_one::<String>("dir").unwrap();
|
||||
let old = sub.get_one::<String>("old").map(|s| s.as_str());
|
||||
let newv = sub.get_one::<String>("new").map(|s| s.as_str());
|
||||
"change-password" => {
|
||||
let dir = sub_args.next_argument::<String>().unwrap();
|
||||
let old = sub_args.next_argument::<String>();
|
||||
let old = old.as_deref();
|
||||
let newv = sub_args.next_argument::<String>();
|
||||
let newv = newv.as_deref();
|
||||
|
||||
match backup_client
|
||||
.change_password_from_path(Path::new(dir), old, newv)
|
||||
.change_password_from_path(Path::new(&dir), old, newv)
|
||||
.await
|
||||
{
|
||||
Ok(_) => println!("Change password finished"),
|
||||
Err(e) => eprintln!("Change password failed: {e}"),
|
||||
}
|
||||
}
|
||||
Some(("erase-device", sub)) => {
|
||||
let dir = sub.get_one::<String>("dir").unwrap();
|
||||
match backup_client.erase_device_from_path(Path::new(dir)).await {
|
||||
"erase-device" => {
|
||||
let dir = sub_args.next_argument::<String>().unwrap();
|
||||
match backup_client.erase_device_from_path(Path::new(&dir)).await {
|
||||
Ok(_) => println!("Erase device command sent"),
|
||||
Err(e) => eprintln!("Erase device failed: {e}"),
|
||||
}
|
||||
}
|
||||
Some(("freespace", _)) => match backup_client.get_freespace().await {
|
||||
"freespace" => match backup_client.get_freespace().await {
|
||||
Ok(freespace) => {
|
||||
let freespace_gb = freespace as f64 / (1024.0 * 1024.0 * 1024.0);
|
||||
println!("Free space: {freespace} bytes ({freespace_gb:.2} GB)");
|
||||
}
|
||||
Err(e) => eprintln!("Failed to get free space: {e}"),
|
||||
},
|
||||
Some(("encryption", _)) => match backup_client.check_backup_encryption().await {
|
||||
"encryption" => match backup_client.check_backup_encryption().await {
|
||||
Ok(is_encrypted) => {
|
||||
println!(
|
||||
"Backup encryption: {}",
|
||||
|
||||
@@ -3,90 +3,62 @@
|
||||
|
||||
use std::{io::Write, path::PathBuf};
|
||||
|
||||
use clap::{Arg, Command, arg, value_parser};
|
||||
use idevice::{
|
||||
IdeviceService, lockdown::LockdownClient, mobile_image_mounter::ImageMounter,
|
||||
pretty_print_plist,
|
||||
provider::IdeviceProvider,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand, JkFlag};
|
||||
use plist_macro::pretty_print_plist;
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("core_device_proxy_tun")
|
||||
.about("Start a tunnel")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Manage mounts on an iOS device")
|
||||
.with_subcommand(
|
||||
"list",
|
||||
JkCommand::new().help("Lists the images mounted on the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
.with_subcommand(
|
||||
"lookup",
|
||||
JkCommand::new().help("Lookup the image signature on the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
.with_subcommand(
|
||||
"unmount",
|
||||
JkCommand::new().help("Unmounts the developer disk image"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(Command::new("list").about("Lists the images mounted on the device"))
|
||||
.subcommand(Command::new("unmount").about("Unmounts the developer disk image"))
|
||||
.subcommand(
|
||||
Command::new("mount")
|
||||
.about("Mounts the developer disk image")
|
||||
.arg(
|
||||
arg!(-i --image <FILE> "the developer disk image to mount")
|
||||
.value_parser(value_parser!(PathBuf))
|
||||
.with_subcommand(
|
||||
"mount",
|
||||
JkCommand::new()
|
||||
.help("Mounts the developer disk image")
|
||||
.with_flag(
|
||||
JkFlag::new("image")
|
||||
.with_short("i")
|
||||
.with_argument(JkArgument::new().required(true))
|
||||
.with_help("A path to the image to mount")
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
arg!(-b --manifest <FILE> "the build manifest (iOS 17+)")
|
||||
.value_parser(value_parser!(PathBuf)),
|
||||
.with_flag(
|
||||
JkFlag::new("manifest")
|
||||
.with_short("b")
|
||||
.with_argument(JkArgument::new())
|
||||
.with_help("the build manifest (iOS 17+)"),
|
||||
)
|
||||
.arg(
|
||||
arg!(-t --trustcache <FILE> "the trust cache (iOS 17+)")
|
||||
.value_parser(value_parser!(PathBuf)),
|
||||
.with_flag(
|
||||
JkFlag::new("trustcache")
|
||||
.with_short("t")
|
||||
.with_argument(JkArgument::new())
|
||||
.with_help("the trust cache (iOS 17+)"),
|
||||
)
|
||||
.arg(
|
||||
arg!(-s --signature <FILE> "the image signature (iOS < 17.0")
|
||||
.value_parser(value_parser!(PathBuf)),
|
||||
.with_flag(
|
||||
JkFlag::new("signature")
|
||||
.with_short("s")
|
||||
.with_argument(JkArgument::new())
|
||||
.with_help("the image signature (iOS < 17.0"),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!(
|
||||
"mounter - query and manage images mounted on a device. Reimplementation of libimobiledevice's binary."
|
||||
);
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "ideviceinfo-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut lockdown_client = LockdownClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Unable to connect to lockdown");
|
||||
@@ -119,114 +91,131 @@ async fn main() {
|
||||
.await
|
||||
.expect("Unable to connect to image mounter");
|
||||
|
||||
if matches.subcommand_matches("list").is_some() {
|
||||
let images = mounter_client
|
||||
.copy_devices()
|
||||
.await
|
||||
.expect("Unable to get images");
|
||||
for i in images {
|
||||
println!("{}", pretty_print_plist(&i));
|
||||
}
|
||||
} else if matches.subcommand_matches("unmount").is_some() {
|
||||
if product_version < 17 {
|
||||
mounter_client
|
||||
.unmount_image("/Developer")
|
||||
let (subcommand, sub_args) = arguments
|
||||
.first_subcommand()
|
||||
.expect("No subcommand passed! Pass -h for help");
|
||||
|
||||
match subcommand.as_str() {
|
||||
"list" => {
|
||||
let images = mounter_client
|
||||
.copy_devices()
|
||||
.await
|
||||
.expect("Failed to unmount");
|
||||
} else {
|
||||
mounter_client
|
||||
.unmount_image("/System/Developer")
|
||||
.await
|
||||
.expect("Failed to unmount");
|
||||
}
|
||||
} else if let Some(matches) = matches.subcommand_matches("mount") {
|
||||
let image: &PathBuf = match matches.get_one("image") {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
eprintln!("No image was passed! Pass -h for help");
|
||||
return;
|
||||
.expect("Unable to get images");
|
||||
for i in images {
|
||||
println!("{}", pretty_print_plist(&i));
|
||||
}
|
||||
};
|
||||
let image = tokio::fs::read(image).await.expect("Unable to read image");
|
||||
if product_version < 17 {
|
||||
let signature: &PathBuf = match matches.get_one("signature") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
eprintln!("No signature was passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let signature = tokio::fs::read(signature)
|
||||
.await
|
||||
.expect("Unable to read signature");
|
||||
|
||||
mounter_client
|
||||
.mount_developer(&image, signature)
|
||||
.await
|
||||
.expect("Unable to mount");
|
||||
} else {
|
||||
let manifest: &PathBuf = match matches.get_one("manifest") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
eprintln!("No build manifest was passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let build_manifest = &tokio::fs::read(manifest)
|
||||
.await
|
||||
.expect("Unable to read signature");
|
||||
|
||||
let trust_cache: &PathBuf = match matches.get_one("trustcache") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
eprintln!("No trust cache was passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let trust_cache = tokio::fs::read(trust_cache)
|
||||
.await
|
||||
.expect("Unable to read signature");
|
||||
|
||||
let unique_chip_id =
|
||||
match lockdown_client.get_value(Some("UniqueChipID"), None).await {
|
||||
Ok(u) => u,
|
||||
Err(_) => {
|
||||
lockdown_client
|
||||
.start_session(&provider.get_pairing_file().await.unwrap())
|
||||
.await
|
||||
.expect("Unable to start session");
|
||||
lockdown_client
|
||||
.get_value(Some("UniqueChipID"), None)
|
||||
.await
|
||||
.expect("Unable to get UniqueChipID")
|
||||
}
|
||||
}
|
||||
.as_unsigned_integer()
|
||||
.expect("Unexpected value for chip IP");
|
||||
|
||||
mounter_client
|
||||
.mount_personalized_with_callback(
|
||||
&*provider,
|
||||
image,
|
||||
trust_cache,
|
||||
build_manifest,
|
||||
None,
|
||||
unique_chip_id,
|
||||
async |((n, d), _)| {
|
||||
let percent = (n as f64 / d as f64) * 100.0;
|
||||
print!("\rProgress: {percent:.2}%");
|
||||
std::io::stdout().flush().unwrap(); // Make sure it prints immediately
|
||||
if n == d {
|
||||
println!();
|
||||
}
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
.expect("Unable to mount");
|
||||
}
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
"lookup" => {
|
||||
let sig = mounter_client
|
||||
.lookup_image(if product_version < 17 {
|
||||
"Developer"
|
||||
} else {
|
||||
"Personalized"
|
||||
})
|
||||
.await
|
||||
.expect("Failed to lookup images");
|
||||
println!("Image signature: {sig:02X?}");
|
||||
}
|
||||
"unmount" => {
|
||||
if product_version < 17 {
|
||||
mounter_client
|
||||
.unmount_image("/Developer")
|
||||
.await
|
||||
.expect("Failed to unmount");
|
||||
} else {
|
||||
mounter_client
|
||||
.unmount_image("/System/Developer")
|
||||
.await
|
||||
.expect("Failed to unmount");
|
||||
}
|
||||
}
|
||||
"mount" => {
|
||||
let image: PathBuf = match sub_args.get_flag("image") {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
eprintln!("No image was passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let image = tokio::fs::read(image).await.expect("Unable to read image");
|
||||
if product_version < 17 {
|
||||
let signature: PathBuf = match sub_args.get_flag("signature") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
eprintln!("No signature was passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let signature = tokio::fs::read(signature)
|
||||
.await
|
||||
.expect("Unable to read signature");
|
||||
|
||||
mounter_client
|
||||
.mount_developer(&image, signature)
|
||||
.await
|
||||
.expect("Unable to mount");
|
||||
} else {
|
||||
let manifest: PathBuf = match sub_args.get_flag("manifest") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
eprintln!("No build manifest was passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let build_manifest = &tokio::fs::read(manifest)
|
||||
.await
|
||||
.expect("Unable to read signature");
|
||||
|
||||
let trust_cache: PathBuf = match sub_args.get_flag("trustcache") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
eprintln!("No trust cache was passed! Pass -h for help");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let trust_cache = tokio::fs::read(trust_cache)
|
||||
.await
|
||||
.expect("Unable to read signature");
|
||||
|
||||
let unique_chip_id =
|
||||
match lockdown_client.get_value(Some("UniqueChipID"), None).await {
|
||||
Ok(u) => u,
|
||||
Err(_) => {
|
||||
lockdown_client
|
||||
.start_session(&provider.get_pairing_file().await.unwrap())
|
||||
.await
|
||||
.expect("Unable to start session");
|
||||
lockdown_client
|
||||
.get_value(Some("UniqueChipID"), None)
|
||||
.await
|
||||
.expect("Unable to get UniqueChipID")
|
||||
}
|
||||
}
|
||||
.as_unsigned_integer()
|
||||
.expect("Unexpected value for chip IP");
|
||||
|
||||
mounter_client
|
||||
.mount_personalized_with_callback(
|
||||
&*provider,
|
||||
image,
|
||||
trust_cache,
|
||||
build_manifest,
|
||||
None,
|
||||
unique_chip_id,
|
||||
async |((n, d), _)| {
|
||||
let percent = (n as f64 / d as f64) * 100.0;
|
||||
print!("\rProgress: {percent:.2}%");
|
||||
std::io::stdout().flush().unwrap(); // Make sure it prints immediately
|
||||
if n == d {
|
||||
println!();
|
||||
}
|
||||
},
|
||||
(),
|
||||
)
|
||||
.await
|
||||
.expect("Unable to mount");
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
85
tools/src/notification_proxy_client.rs
Normal file
85
tools/src/notification_proxy_client.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use idevice::{
|
||||
IdeviceService, notification_proxy::NotificationProxyClient, provider::IdeviceProvider,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Notification proxy")
|
||||
.with_subcommand(
|
||||
"observe",
|
||||
JkCommand::new()
|
||||
.help("Observe notifications from the device")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("The notification ID to observe")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.with_subcommand(
|
||||
"post",
|
||||
JkCommand::new()
|
||||
.help("Post a notification to the device")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("The notification ID to post")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut client = NotificationProxyClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Unable to connect to notification proxy");
|
||||
|
||||
let (subcommand, sub_args) = arguments.first_subcommand().unwrap();
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
match subcommand.as_str() {
|
||||
"observe" => {
|
||||
let input: String = sub_args
|
||||
.next_argument::<String>()
|
||||
.expect("No notification ID passed");
|
||||
|
||||
let notifications: Vec<&str> = input.split_whitespace().collect();
|
||||
client
|
||||
.observe_notifications(¬ifications)
|
||||
.await
|
||||
.expect("Failed to observe notifications");
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
println!("\nShutdown signal received, exiting.");
|
||||
break;
|
||||
}
|
||||
|
||||
result = client.receive_notification() => {
|
||||
match result {
|
||||
Ok(notif) => println!("Received notification: {}", notif),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to receive notification: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"post" => {
|
||||
let notification: String = sub_args
|
||||
.next_argument::<String>()
|
||||
.expect("No notification ID passed");
|
||||
|
||||
client
|
||||
.post_notification(¬ification)
|
||||
.await
|
||||
.expect("Failed to post notification");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,16 @@
|
||||
// Monitor memory and app notifications
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake};
|
||||
mod common;
|
||||
use idevice::{
|
||||
IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider,
|
||||
rsd::RsdHandshake,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkCommand};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
let matches = Command::new("notifications")
|
||||
.about("start notifications")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
print!("notifications - start notifications to ios device");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "notifications-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new().help("Notification proxy")
|
||||
}
|
||||
|
||||
pub async fn main(_arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let proxy = CoreDeviceProxy::connect(&*provider)
|
||||
.await
|
||||
.expect("no core proxy");
|
||||
@@ -80,7 +37,6 @@ async fn main() {
|
||||
.await
|
||||
.expect("Failed to start notifications");
|
||||
|
||||
// Handle Ctrl+C gracefully
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
@@ -88,7 +44,6 @@ async fn main() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Branch 2: Wait for the next batch of notifications.
|
||||
result = notification_client.get_notification() => {
|
||||
if let Err(e) = result {
|
||||
eprintln!("Failed to get notifications: {}", e);
|
||||
|
||||
@@ -1,58 +1,13 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{IdeviceService, os_trace_relay::OsTraceRelayClient};
|
||||
use idevice::{IdeviceService, os_trace_relay::OsTraceRelayClient, provider::IdeviceProvider};
|
||||
use jkcli::{CollectedArguments, JkCommand};
|
||||
|
||||
mod common;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new().help("Relay OS logs")
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("os_trace_relay")
|
||||
.about("Relay system logs")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("Relay logs on the device");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "misagent-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(_arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let log_client = OsTraceRelayClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Unable to connect to misagent");
|
||||
|
||||
@@ -1,46 +1,35 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService,
|
||||
lockdown::LockdownClient,
|
||||
provider::IdeviceProvider,
|
||||
usbmuxd::{Connection, UsbmuxdAddr, UsbmuxdConnection},
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand, JkFlag};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("pair")
|
||||
.about("Pair with the device")
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Manage files in the AFC jail of a device")
|
||||
.with_argument(JkArgument::new().with_help("A UDID to override and pair with"))
|
||||
.with_flag(
|
||||
JkFlag::new("name")
|
||||
.with_help("The host name to report to the device")
|
||||
.with_argument(JkArgument::new().required(true))
|
||||
.with_short("n"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
}
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("pair - pair with the device");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
pub async fn main(arguments: &CollectedArguments, _provider: Box<dyn IdeviceProvider>) {
|
||||
let mut arguments = arguments.clone();
|
||||
let udid: Option<String> = arguments.next_argument();
|
||||
|
||||
let mut u = UsbmuxdConnection::default()
|
||||
.await
|
||||
.expect("Failed to connect to usbmuxd");
|
||||
let dev = match udid {
|
||||
Some(udid) => u
|
||||
.get_device(udid)
|
||||
.get_device(udid.as_str())
|
||||
.await
|
||||
.expect("Failed to get device with specific udid"),
|
||||
None => u
|
||||
@@ -62,8 +51,11 @@ async fn main() {
|
||||
};
|
||||
let id = uuid::Uuid::new_v4().to_string().to_uppercase();
|
||||
|
||||
let name = arguments.get_flag::<String>("name");
|
||||
let name = name.as_deref();
|
||||
|
||||
let mut pairing_file = lockdown_client
|
||||
.pair(id, u.get_buid().await.unwrap())
|
||||
.pair(id, u.get_buid().await.unwrap(), name)
|
||||
.await
|
||||
.expect("Failed to pair");
|
||||
|
||||
|
||||
@@ -1,55 +1,20 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService,
|
||||
pcapd::{PcapFileWriter, PcapdClient},
|
||||
provider::IdeviceProvider,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
mod common;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Writes pcap network data")
|
||||
.with_argument(JkArgument::new().with_help("Write PCAP to this file (use '-' for stdout)"))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("pcapd")
|
||||
.about("Capture IP packets")
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("out")
|
||||
.long("out")
|
||||
.value_name("PCAP")
|
||||
.help("Write PCAP to this file (use '-' for stdout)"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("bt_packet_logger - capture bluetooth packets");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let out = matches.get_one::<String>("out").map(String::to_owned);
|
||||
|
||||
let provider = match common::get_provider(udid, None, None, "pcapd-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let out = arguments.clone().next_argument::<String>();
|
||||
|
||||
let mut logger_client = PcapdClient::connect(&*provider)
|
||||
.await
|
||||
|
||||
@@ -1,76 +1,34 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{IdeviceService, preboard_service::PreboardServiceClient};
|
||||
use idevice::{IdeviceService, preboard_service::PreboardServiceClient, provider::IdeviceProvider};
|
||||
use jkcli::{CollectedArguments, JkCommand};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("preboard")
|
||||
.about("Mess with developer mode")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(Command::new("create").about("Create a stashbag??"))
|
||||
.subcommand(Command::new("commit").about("Commit a stashbag??"))
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("preboard - no idea what this does");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "amfi-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Interact with the preboard service")
|
||||
.with_subcommand("create", JkCommand::new().help("Create a stashbag??"))
|
||||
.with_subcommand("commit", JkCommand::new().help("Commit a stashbag??"))
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut pc = PreboardServiceClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Failed to connect to Preboard");
|
||||
|
||||
if matches.subcommand_matches("create").is_some() {
|
||||
pc.create_stashbag(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
|
||||
.await
|
||||
.expect("Failed to create");
|
||||
} else if matches.subcommand_matches("commit").is_some() {
|
||||
pc.commit_stashbag(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
|
||||
.await
|
||||
.expect("Failed to create");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
let (sub_name, _) = arguments.first_subcommand().unwrap();
|
||||
|
||||
match sub_name.as_str() {
|
||||
"create" => {
|
||||
pc.create_stashbag(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
|
||||
.await
|
||||
.expect("Failed to create");
|
||||
}
|
||||
"commit" => {
|
||||
pc.commit_stashbag(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
|
||||
.await
|
||||
.expect("Failed to create");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,76 +1,26 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::provider::IdeviceProvider;
|
||||
use idevice::services::lockdown::LockdownClient;
|
||||
use idevice::{IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
|
||||
mod common;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Launch an app with process control")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("The bundle ID to launch"),
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("process_control")
|
||||
.about("Query process control")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(2),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("tunneld")
|
||||
.long("tunneld")
|
||||
.help("Use tunneld for connection")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("bundle_id")
|
||||
.value_name("Bundle ID")
|
||||
.help("Bundle ID of the app to launch")
|
||||
.index(1),
|
||||
)
|
||||
.get_matches();
|
||||
let mut arguments = arguments.clone();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("process_control - launch and manage processes on the device");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let bundle_id = matches
|
||||
.get_one::<String>("bundle_id")
|
||||
.expect("No bundle ID specified");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "process_control-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let bundle_id: String = arguments.next_argument().expect("No bundle ID specified");
|
||||
|
||||
let mut rs_client_opt: Option<
|
||||
idevice::dvt::remote_server::RemoteServerClient<Box<dyn idevice::ReadWrite>>,
|
||||
|
||||
@@ -1,65 +1,17 @@
|
||||
// Jackson Coxson
|
||||
// Print out all the RemoteXPC services
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService, core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake,
|
||||
tcp::stream::AdapterStream,
|
||||
IdeviceService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider,
|
||||
rsd::RsdHandshake, tcp::stream::AdapterStream,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkCommand};
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("remotexpc")
|
||||
.about("Get services from RemoteXPC")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("remotexpc - get info from RemoteXPC");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
let host = matches.get_one::<String>("host");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "remotexpc-jkcoxson").await
|
||||
{
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new().help("Get services from RemoteXPC")
|
||||
}
|
||||
|
||||
pub async fn main(_arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let proxy = CoreDeviceProxy::connect(&*provider)
|
||||
.await
|
||||
.expect("no core proxy");
|
||||
|
||||
@@ -1,76 +1,41 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{
|
||||
IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, pretty_print_dictionary,
|
||||
IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider,
|
||||
restore_service::RestoreServiceClient, rsd::RsdHandshake,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
use plist_macro::pretty_print_dictionary;
|
||||
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("restore_service")
|
||||
.about("Interact with the Restore Service service")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Interact with the Restore Service service")
|
||||
.with_subcommand("delay", JkCommand::new().help("Delay recovery image"))
|
||||
.with_subcommand("recovery", JkCommand::new().help("Enter recovery mode"))
|
||||
.with_subcommand("reboot", JkCommand::new().help("Reboots the device"))
|
||||
.with_subcommand(
|
||||
"preflightinfo",
|
||||
JkCommand::new().help("Gets the preflight info"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
.with_subcommand("nonces", JkCommand::new().help("Gets the nonces"))
|
||||
.with_subcommand(
|
||||
"app_parameters",
|
||||
JkCommand::new().help("Gets the app parameters"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)"),
|
||||
.with_subcommand(
|
||||
"restore_lang",
|
||||
JkCommand::new()
|
||||
.help("Restores the language")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.required(true)
|
||||
.with_help("Language to restore"),
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.subcommand(Command::new("delay").about("Delay recovery image"))
|
||||
.subcommand(Command::new("recovery").about("Enter recovery mode"))
|
||||
.subcommand(Command::new("reboot").about("Reboots the device"))
|
||||
.subcommand(Command::new("preflightinfo").about("Gets the preflight info"))
|
||||
.subcommand(Command::new("nonces").about("Gets the nonces"))
|
||||
.subcommand(Command::new("app_parameters").about("Gets the app parameters"))
|
||||
.subcommand(
|
||||
Command::new("restore_lang")
|
||||
.about("Restores the language")
|
||||
.arg(Arg::new("language").required(true).index(1)),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!(
|
||||
"mounter - query and manage images mounted on a device. Reimplementation of libimobiledevice's binary."
|
||||
);
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "restore_service-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let proxy = CoreDeviceProxy::connect(&*provider)
|
||||
.await
|
||||
.expect("no core proxy");
|
||||
@@ -88,37 +53,46 @@ async fn main() {
|
||||
.await
|
||||
.expect("Unable to connect to service");
|
||||
|
||||
if matches.subcommand_matches("recovery").is_some() {
|
||||
restore_client
|
||||
.enter_recovery()
|
||||
.await
|
||||
.expect("command failed");
|
||||
} else if matches.subcommand_matches("reboot").is_some() {
|
||||
restore_client.reboot().await.expect("command failed");
|
||||
} else if matches.subcommand_matches("preflightinfo").is_some() {
|
||||
let info = restore_client
|
||||
.get_preflightinfo()
|
||||
.await
|
||||
.expect("command failed");
|
||||
pretty_print_dictionary(&info);
|
||||
} else if matches.subcommand_matches("nonces").is_some() {
|
||||
let nonces = restore_client.get_nonces().await.expect("command failed");
|
||||
pretty_print_dictionary(&nonces);
|
||||
} else if matches.subcommand_matches("app_parameters").is_some() {
|
||||
let params = restore_client
|
||||
.get_app_parameters()
|
||||
.await
|
||||
.expect("command failed");
|
||||
pretty_print_dictionary(¶ms);
|
||||
} else if let Some(matches) = matches.subcommand_matches("restore_lang") {
|
||||
let lang = matches
|
||||
.get_one::<String>("language")
|
||||
.expect("No language passed");
|
||||
restore_client
|
||||
.restore_lang(lang)
|
||||
.await
|
||||
.expect("failed to restore lang");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().unwrap();
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
match sub_name.as_str() {
|
||||
"recovery" => {
|
||||
restore_client
|
||||
.enter_recovery()
|
||||
.await
|
||||
.expect("command failed");
|
||||
}
|
||||
"reboot" => {
|
||||
restore_client.reboot().await.expect("command failed");
|
||||
}
|
||||
"preflightinfo" => {
|
||||
let info = restore_client
|
||||
.get_preflightinfo()
|
||||
.await
|
||||
.expect("command failed");
|
||||
println!("{}", pretty_print_dictionary(&info));
|
||||
}
|
||||
"nonces" => {
|
||||
let nonces = restore_client.get_nonces().await.expect("command failed");
|
||||
println!("{}", pretty_print_dictionary(&nonces));
|
||||
}
|
||||
"app_parameters" => {
|
||||
let params = restore_client
|
||||
.get_app_parameters()
|
||||
.await
|
||||
.expect("command failed");
|
||||
println!("{}", pretty_print_dictionary(¶ms));
|
||||
}
|
||||
"restore_lang" => {
|
||||
let lang: String = sub_args
|
||||
.next_argument::<String>()
|
||||
.expect("No language passed");
|
||||
restore_client
|
||||
.restore_lang(lang)
|
||||
.await
|
||||
.expect("failed to restore lang");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,20 @@
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, rsd::RsdHandshake};
|
||||
use idevice::{
|
||||
IdeviceService, RsdService, core_device_proxy::CoreDeviceProxy, provider::IdeviceProvider,
|
||||
rsd::RsdHandshake,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand};
|
||||
use std::fs;
|
||||
|
||||
use idevice::screenshotr::ScreenshotService;
|
||||
|
||||
mod common;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Take a screenshot")
|
||||
.with_argument(JkArgument::new().with_help("Output path").required(true))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
let matches = Command::new("screen_shot")
|
||||
.about("take screenshot")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)")
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("output")
|
||||
.short('o')
|
||||
.long("output")
|
||||
.value_name("FILE")
|
||||
.help("Output file path for the screenshot (default: ./screenshot.png)")
|
||||
.default_value("screenshot.png"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
print!("screen_shot - take screenshot from ios device");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
let output_path = matches.get_one::<String>("output").unwrap();
|
||||
|
||||
let provider =
|
||||
match common::get_provider(udid, host, pairing_file, "take_screenshot-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let output_path = arguments.clone().next_argument::<String>().unwrap();
|
||||
|
||||
let res = if let Ok(proxy) = CoreDeviceProxy::connect(&*provider).await {
|
||||
println!("Using DVT over CoreDeviceProxy");
|
||||
@@ -104,7 +55,7 @@ async fn main() {
|
||||
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),
|
||||
Err(e) => eprintln!("Failed to write screenshot to file: {}", e),
|
||||
}
|
||||
|
||||
161
tools/src/springboardservices.rs
Normal file
161
tools/src/springboardservices.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use idevice::{
|
||||
IdeviceService, provider::IdeviceProvider, springboardservices::SpringBoardServicesClient,
|
||||
};
|
||||
use jkcli::{CollectedArguments, JkArgument, JkCommand, JkFlag};
|
||||
use plist_macro::{plist_value_to_xml_bytes, pretty_print_plist};
|
||||
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new()
|
||||
.help("Manage the springboard service")
|
||||
.with_subcommand(
|
||||
"get_icon_state",
|
||||
JkCommand::new()
|
||||
.help("Gets the icon state from the device")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Version to query by")
|
||||
.required(false),
|
||||
)
|
||||
.with_flag(
|
||||
JkFlag::new("save")
|
||||
.with_help("Path to save to")
|
||||
.with_argument(JkArgument::new().required(true)),
|
||||
),
|
||||
)
|
||||
.with_subcommand(
|
||||
"set_icon_state",
|
||||
JkCommand::new().help("Sets the icon state").with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("plist to set based on")
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.with_subcommand(
|
||||
"get_wallpaper_preview",
|
||||
JkCommand::new()
|
||||
.help("Gets wallpaper preview")
|
||||
.with_subcommand("homescreen", JkCommand::new())
|
||||
.with_subcommand("lockscreen", JkCommand::new())
|
||||
.subcommand_required(true)
|
||||
.with_flag(
|
||||
JkFlag::new("save")
|
||||
.with_help("Path to save the wallpaper PNG file, or preview.png by default")
|
||||
.with_argument(JkArgument::new().required(true)),
|
||||
),
|
||||
)
|
||||
.with_subcommand(
|
||||
"get_interface_orientation",
|
||||
JkCommand::new().help("Gets the device's current screen orientation"),
|
||||
)
|
||||
.with_subcommand(
|
||||
"get_homescreen_icon_metrics",
|
||||
JkCommand::new().help("Gets home screen icon layout metrics"),
|
||||
)
|
||||
.with_subcommand(
|
||||
"get_icon",
|
||||
JkCommand::new()
|
||||
.help("Gets an app's icon as PNG")
|
||||
.with_argument(
|
||||
JkArgument::new()
|
||||
.with_help("Bundle identifier (e.g. com.apple.Maps)")
|
||||
.required(true),
|
||||
)
|
||||
.with_flag(
|
||||
JkFlag::new("save")
|
||||
.with_help("Path to save the icon PNG file, or icon.png by default")
|
||||
.with_argument(JkArgument::new().required(true)),
|
||||
),
|
||||
)
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
pub async fn main(arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut sbc = SpringBoardServicesClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Failed to connect to springboardservices");
|
||||
|
||||
let (sub_name, sub_args) = arguments.first_subcommand().expect("No subcommand passed");
|
||||
let mut sub_args = sub_args.clone();
|
||||
|
||||
match sub_name.as_str() {
|
||||
"get_icon_state" => {
|
||||
let version: Option<String> = sub_args.next_argument();
|
||||
let version = version.as_deref();
|
||||
let state = sbc
|
||||
.get_icon_state(version)
|
||||
.await
|
||||
.expect("Failed to get icon state");
|
||||
println!("{}", pretty_print_plist(&state));
|
||||
|
||||
if let Some(path) = sub_args.get_flag::<String>("save") {
|
||||
tokio::fs::write(path, plist_value_to_xml_bytes(&state))
|
||||
.await
|
||||
.expect("Failed to save to path");
|
||||
}
|
||||
}
|
||||
"set_icon_state" => {
|
||||
let load_path = sub_args.next_argument::<String>().unwrap();
|
||||
let load = tokio::fs::read(load_path)
|
||||
.await
|
||||
.expect("Failed to read plist");
|
||||
let load: plist::Value =
|
||||
plist::from_bytes(&load).expect("Failed to parse bytes as plist");
|
||||
|
||||
sbc.set_icon_state(load)
|
||||
.await
|
||||
.expect("Failed to set icon state");
|
||||
}
|
||||
"get_wallpaper_preview" => {
|
||||
let (wallpaper_type, _) = sub_args.first_subcommand().unwrap();
|
||||
|
||||
let wallpaper = match wallpaper_type.as_str() {
|
||||
"homescreen" => sbc.get_home_screen_wallpaper_preview_pngdata().await,
|
||||
"lockscreen" => sbc.get_lock_screen_wallpaper_preview_pngdata().await,
|
||||
_ => panic!("Invalid wallpaper type. Use 'homescreen' or 'lockscreen'"),
|
||||
}
|
||||
.expect("Failed to get wallpaper preview");
|
||||
|
||||
let save_path = sub_args
|
||||
.get_flag::<String>("save")
|
||||
.unwrap_or("preview.png".to_string());
|
||||
|
||||
tokio::fs::write(&save_path, wallpaper)
|
||||
.await
|
||||
.expect("Failed to save wallpaper");
|
||||
}
|
||||
"get_interface_orientation" => {
|
||||
let orientation = sbc
|
||||
.get_interface_orientation()
|
||||
.await
|
||||
.expect("Failed to get interface orientation");
|
||||
println!("{:?}", orientation);
|
||||
}
|
||||
"get_homescreen_icon_metrics" => {
|
||||
let metrics = sbc
|
||||
.get_homescreen_icon_metrics()
|
||||
.await
|
||||
.expect("Failed to get homescreen icon metrics");
|
||||
let metrics_value = plist::Value::Dictionary(metrics);
|
||||
println!("{}", pretty_print_plist(&metrics_value));
|
||||
}
|
||||
"get_icon" => {
|
||||
let bundle_id = sub_args.next_argument::<String>().unwrap();
|
||||
|
||||
let icon_data = sbc
|
||||
.get_icon_pngdata(bundle_id)
|
||||
.await
|
||||
.expect("Failed to get icon");
|
||||
|
||||
let save_path = sub_args
|
||||
.get_flag::<String>("save")
|
||||
.unwrap_or("icon.png".to_string());
|
||||
|
||||
tokio::fs::write(&save_path, icon_data)
|
||||
.await
|
||||
.expect("Failed to save icon");
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,13 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use idevice::{IdeviceService, syslog_relay::SyslogRelayClient};
|
||||
use idevice::{IdeviceService, provider::IdeviceProvider, syslog_relay::SyslogRelayClient};
|
||||
use jkcli::{CollectedArguments, JkCommand};
|
||||
|
||||
mod common;
|
||||
pub fn register() -> JkCommand {
|
||||
JkCommand::new().help("Relay system logs")
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let matches = Command::new("syslog_relay")
|
||||
.about("Relay system logs")
|
||||
.arg(
|
||||
Arg::new("host")
|
||||
.long("host")
|
||||
.value_name("HOST")
|
||||
.help("IP address of the device"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pairing_file")
|
||||
.long("pairing-file")
|
||||
.value_name("PATH")
|
||||
.help("Path to the pairing file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("udid")
|
||||
.value_name("UDID")
|
||||
.help("UDID of the device (overrides host/pairing file)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("about")
|
||||
.long("about")
|
||||
.help("Show about information")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
println!("Relay logs on the device");
|
||||
println!("Copyright (c) 2025 Jackson Coxson");
|
||||
return;
|
||||
}
|
||||
|
||||
let udid = matches.get_one::<String>("udid");
|
||||
let host = matches.get_one::<String>("host");
|
||||
let pairing_file = matches.get_one::<String>("pairing_file");
|
||||
|
||||
let provider = match common::get_provider(udid, host, pairing_file, "misagent-jkcoxson").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
pub async fn main(_arguments: &CollectedArguments, provider: Box<dyn IdeviceProvider>) {
|
||||
let mut log_client = SyslogRelayClient::connect(&*provider)
|
||||
.await
|
||||
.expect("Unable to connect to misagent");
|
||||
|
||||
Reference in New Issue
Block a user