diff --git a/idevice/src/afc/file.rs b/idevice/src/afc/file.rs new file mode 100644 index 0000000..43798e7 --- /dev/null +++ b/idevice/src/afc/file.rs @@ -0,0 +1,76 @@ +// Jackson Coxson + +use crate::IdeviceError; + +use super::{ + opcode::AfcOpcode, + packet::{AfcPacket, AfcPacketHeader}, +}; + +const MAX_TRANSFER: u64 = 64 * 1024; // this is what go-ios uses + +pub struct FileDescriptor<'a> { + pub(crate) client: &'a mut super::AfcClient, + pub(crate) fd: u64, + pub(crate) path: String, +} + +impl FileDescriptor<'_> { + pub async fn close(self) -> Result<(), IdeviceError> { + let header_payload = self.fd.to_le_bytes().to_vec(); + let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN; + + let header = AfcPacketHeader { + magic: super::MAGIC, + entire_len: header_len, // it's the same since the payload is empty for this + header_payload_len: header_len, + packet_num: self.client.package_number, + operation: AfcOpcode::FileClose, + }; + self.client.package_number += 1; + + let packet = AfcPacket { + header, + header_payload, + payload: Vec::new(), + }; + + self.client.send(packet).await?; + self.client.read().await?; + Ok(()) + } + + pub async fn read(&mut self) -> Result, IdeviceError> { + // Get the file size first + let mut bytes_left = self.client.get_file_info(&self.path).await?.size; + let mut collected_bytes = Vec::with_capacity(bytes_left); + + while bytes_left > 0 { + let mut header_payload = self.fd.to_le_bytes().to_vec(); + header_payload.extend_from_slice(&MAX_TRANSFER.to_le_bytes()); + let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN; + + let header = AfcPacketHeader { + magic: super::MAGIC, + entire_len: header_len, // it's the same since the payload is empty for this + header_payload_len: header_len, + packet_num: self.client.package_number, + operation: AfcOpcode::Read, + }; + self.client.package_number += 1; + + let packet = AfcPacket { + header, + header_payload, + payload: Vec::new(), + }; + + self.client.send(packet).await?; + let res = self.client.read().await?; + bytes_left -= res.payload.len(); + collected_bytes.extend(res.payload); + } + + Ok(collected_bytes) + } +} diff --git a/idevice/src/afc/mod.rs b/idevice/src/afc/mod.rs index 76ecbbb..bc04af3 100644 --- a/idevice/src/afc/mod.rs +++ b/idevice/src/afc/mod.rs @@ -3,13 +3,15 @@ use std::collections::HashMap; use errors::AfcError; +use file::FileDescriptor; use log::warn; -use opcode::AfcOpcode; +use opcode::{AfcFopenMode, AfcOpcode}; use packet::{AfcPacket, AfcPacketHeader}; use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService}; pub mod errors; +pub mod file; pub mod opcode; pub mod packet; @@ -329,6 +331,45 @@ impl AfcClient { Ok(()) } + pub async fn open( + &mut self, + path: impl Into, + mode: AfcFopenMode, + ) -> Result { + let path = path.into(); + let mut header_payload = (mode as u64).to_le_bytes().to_vec(); + header_payload.extend(path.as_bytes()); + let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN; + + let header = AfcPacketHeader { + magic: MAGIC, + entire_len: header_len, // it's the same since the payload is empty for this + header_payload_len: header_len, + packet_num: self.package_number, + operation: AfcOpcode::FileOpen, + }; + self.package_number += 1; + + let packet = AfcPacket { + header, + header_payload, + payload: Vec::new(), + }; + + self.send(packet).await?; + let res = self.read().await?; + if res.header_payload.len() < 8 { + warn!("Header payload fd is less than 8 bytes"); + return Err(IdeviceError::UnexpectedResponse); + } + let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap()); + Ok(FileDescriptor { + client: self, + fd, + path, + }) + } + pub async fn read(&mut self) -> Result { let res = AfcPacket::read(&mut self.idevice).await?; if res.header.operation == AfcOpcode::Status { diff --git a/idevice/src/afc/opcode.rs b/idevice/src/afc/opcode.rs index e550426..28791f2 100644 --- a/idevice/src/afc/opcode.rs +++ b/idevice/src/afc/opcode.rs @@ -35,6 +35,7 @@ pub enum AfcOpcode { RemovePathAndContents = 0x00000022, } +#[repr(u64)] pub enum AfcFopenMode { RdOnly = 0x00000001, // r O_RDONLY Rw = 0x00000002, // r+ O_RDWR | O_CREAT diff --git a/tools/src/afc.rs b/tools/src/afc.rs index 0e84759..276718f 100644 --- a/tools/src/afc.rs +++ b/tools/src/afc.rs @@ -1,7 +1,10 @@ // Jackson Coxson use clap::{Arg, Command}; -use idevice::{afc::AfcClient, IdeviceService}; +use idevice::{ + afc::{opcode::AfcFopenMode, AfcClient}, + IdeviceService, +}; mod common; @@ -39,6 +42,12 @@ async fn main() { .about("Lists the items in the directory") .arg(Arg::new("path").required(true).index(1)), ) + .subcommand( + Command::new("download") + .about("Creates a directory") + .arg(Arg::new("path").required(true).index(1)) + .arg(Arg::new("save").required(true).index(2)), + ) .subcommand( Command::new("mkdir") .about("Creates a directory") @@ -90,6 +99,19 @@ async fn main() { } else if let Some(matches) = matches.subcommand_matches("mkdir") { let path = matches.get_one::("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::("path").expect("No path passed"); + let save = matches.get_one::("save").expect("No path passed"); + + let mut file = afc_client + .open(path, AfcFopenMode::RdOnly) + .await + .expect("Failed to open"); + + let res = file.read().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("remove") { let path = matches.get_one::("path").expect("No path passed"); afc_client.remove(path).await.expect("Failed to remove");