use restorekit::{device, Result};
fn print_ports() {
let ports = restorekit::dfu::ports();
if ports.is_empty() {
return;
}
println!("Host USB-C ports:\n");
for p in &ports {
let loc = p.location.as_deref().unwrap_or("(unlabeled)");
let dfu = if p.dfu { " — DFU-capable" } else { "" };
println!(" [rid {}] {loc}{dfu}", p.rid);
}
println!();
}
fn print_dongles() {
let dongles = match restorekit::dongle::list() {
Ok(d) if !d.is_empty() => d,
_ => return,
};
println!("RecoverKit dongles:\n");
for d in &dongles {
match d.attached_device() {
Ok(Some(dev)) => println!(
" {} ({}) — {} [{} mode]",
d.serial,
d.product,
dev.display_name(),
dev.mode
),
_ => println!(
" {} ({}) — no target visible on this host",
d.serial, d.product
),
}
}
println!();
}
pub fn run(json: bool, watch: bool) -> Result<()> {
if !watch {
return run_once(json);
}
loop {
if !json {
print!("\x1b[2J\x1b[H");
}
run_once(json)?;
std::thread::sleep(std::time::Duration::from_secs(2));
}
}
fn run_once(json: bool) -> Result<()> {
let mut devices = device::list()?;
device::identify(&mut devices);
if json {
use serde_json::json;
let devs: Vec<serde_json::Value> = devices
.iter()
.map(|d| {
let conn = restorekit::dongle::connection_for(d);
let host_dfu = conn.host_reachable() && d.port.as_ref().is_some_and(|p| p.dfu);
let mut v = serde_json::to_value(d).unwrap_or_else(|_| json!({}));
if let Some(obj) = v.as_object_mut() {
obj.insert("connection".into(), json!(conn.kind()));
obj.insert("via_dongle".into(), json!(conn.dongle()));
obj.insert("host_dfu_capable".into(), json!(host_dfu));
}
v
})
.collect();
let host_ports: Vec<serde_json::Value> = restorekit::dfu::ports()
.into_iter()
.map(|p| json!({ "rid": p.rid, "location": p.location, "dfu": p.dfu }))
.collect();
let dongles: Vec<serde_json::Value> = restorekit::dongle::list()
.unwrap_or_default()
.into_iter()
.map(|d| {
json!({
"serial": d.serial,
"product": d.product,
"model": d.model,
"fw_version": d.fw_version().ok(),
"targets": d.attached_device().ok().flatten().into_iter().collect::<Vec<_>>(),
})
})
.collect();
println!(
"{}",
json!({ "devices": devs, "host_ports": host_ports, "dongles": dongles })
);
return Ok(());
}
if devices.is_empty() {
println!("No Apple devices found on USB.");
if !restorekit::host_can_trigger_dfu() {
println!("\n{}", restorekit::manual_dfu_instructions());
} else {
println!("Cable a target Mac to the DFU port and run `restorekit dfu`.");
}
print_ports();
print_dongles();
return Ok(());
}
println!(
"Found {} Apple device{}:\n",
devices.len(),
if devices.len() == 1 { "" } else { "s" }
);
let dfu_port = restorekit::dfu::dfu_port_label();
for d in &devices {
println!(" {} [{} mode]", d.display_name(), d.mode);
println!(" usb: {}", d.usb.summary());
if let Some(id) = d.identifier() {
println!(" identifier: {id}");
}
if let Some(i) = &d.identity {
println!(" chip: CPID:{:04x} board: BDID:{:02x}", i.cpid, i.bdid);
}
if let Some(ecid) = d.ecid {
println!(" ECID: 0x{ecid:x}");
}
if let Some(srtg) = d.srtg() {
println!(" iBoot: {srtg}");
}
if d.is_billboard() {
if !d.serial.is_empty() {
println!(
" port controller UID: {} (not the Mac's serial)",
d.serial
);
}
if let Some(alt) = d.billboard_alt_mode() {
println!(" alt mode tried: {alt}");
}
println!(
" → port controller only; no model, serial, or ECID in this mode. Put it in DFU to read them."
);
} else if d.identity.is_none() && !d.serial.is_empty() {
println!(" serial: {}", d.serial);
}
match restorekit::dongle::connection_for(d) {
restorekit::Connection::Dongle(id) => {
let here = d.port.as_ref().and_then(|p| p.location.as_deref());
match here {
Some(loc) => println!(" connection: via dongle {id} on {loc}"),
None => println!(" connection: via dongle {id}"),
}
println!(
" → host DFU can't reach it through the dongle; `restorekit dfu` triggers it over the dongle"
);
}
restorekit::Connection::Hub => {
let here = d.port.as_ref().and_then(|p| p.location.as_deref());
match here {
Some(loc) => println!(" connection: behind a USB hub on {loc}"),
None => println!(" connection: behind a USB hub"),
}
println!(" → no DFU path from this host; cable it straight to the DFU port");
}
restorekit::Connection::Direct => {
if let Some(port) = &d.port {
let here = port.location.as_deref().unwrap_or("this port");
if port.dfu {
println!(" port: {here} [rid {}] (the DFU port)", port.rid);
} else {
match dfu_port.as_deref() {
Some(name) => println!(
" port: {here} [rid {}] — move the cable to {name} to restore",
port.rid
),
None => {
println!(" port: {here} [rid {}] — not the DFU port", port.rid)
}
}
}
}
}
}
println!();
}
print_ports();
print_dongles();
if !devices.iter().any(|d| d.restorable()) {
println!("None are in DFU mode; only a Mac in DFU mode can be restored.");
if restorekit::host_can_trigger_dfu() {
println!("Run `restorekit dfu` to put the cabled target into DFU mode.");
}
}
Ok(())
}