afc pull files

This commit is contained in:
Jackson Coxson
2025-04-06 11:38:28 -06:00
parent bd3920a46c
commit 7b0590b7ae
4 changed files with 142 additions and 2 deletions

76
idevice/src/afc/file.rs Normal file
View File

@@ -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<Vec<u8>, 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)
}
}

View File

@@ -3,13 +3,15 @@
use std::collections::HashMap; use std::collections::HashMap;
use errors::AfcError; use errors::AfcError;
use file::FileDescriptor;
use log::warn; use log::warn;
use opcode::AfcOpcode; use opcode::{AfcFopenMode, AfcOpcode};
use packet::{AfcPacket, AfcPacketHeader}; use packet::{AfcPacket, AfcPacketHeader};
use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService}; use crate::{lockdown::LockdownClient, Idevice, IdeviceError, IdeviceService};
pub mod errors; pub mod errors;
pub mod file;
pub mod opcode; pub mod opcode;
pub mod packet; pub mod packet;
@@ -329,6 +331,45 @@ impl AfcClient {
Ok(()) Ok(())
} }
pub async fn open(
&mut self,
path: impl Into<String>,
mode: AfcFopenMode,
) -> Result<FileDescriptor, IdeviceError> {
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<AfcPacket, IdeviceError> { pub async fn read(&mut self) -> Result<AfcPacket, IdeviceError> {
let res = AfcPacket::read(&mut self.idevice).await?; let res = AfcPacket::read(&mut self.idevice).await?;
if res.header.operation == AfcOpcode::Status { if res.header.operation == AfcOpcode::Status {

View File

@@ -35,6 +35,7 @@ pub enum AfcOpcode {
RemovePathAndContents = 0x00000022, RemovePathAndContents = 0x00000022,
} }
#[repr(u64)]
pub enum AfcFopenMode { pub enum AfcFopenMode {
RdOnly = 0x00000001, // r O_RDONLY RdOnly = 0x00000001, // r O_RDONLY
Rw = 0x00000002, // r+ O_RDWR | O_CREAT Rw = 0x00000002, // r+ O_RDWR | O_CREAT

View File

@@ -1,7 +1,10 @@
// Jackson Coxson // Jackson Coxson
use clap::{Arg, Command}; use clap::{Arg, Command};
use idevice::{afc::AfcClient, IdeviceService}; use idevice::{
afc::{opcode::AfcFopenMode, AfcClient},
IdeviceService,
};
mod common; mod common;
@@ -39,6 +42,12 @@ async fn main() {
.about("Lists the items in the directory") .about("Lists the items in the directory")
.arg(Arg::new("path").required(true).index(1)), .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( .subcommand(
Command::new("mkdir") Command::new("mkdir")
.about("Creates a directory") .about("Creates a directory")
@@ -90,6 +99,19 @@ async fn main() {
} else if let Some(matches) = matches.subcommand_matches("mkdir") { } else if let Some(matches) = matches.subcommand_matches("mkdir") {
let path = matches.get_one::<String>("path").expect("No path passed"); let path = matches.get_one::<String>("path").expect("No path passed");
afc_client.mk_dir(path).await.expect("Failed to mkdir"); 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 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") { } else if let Some(matches) = matches.subcommand_matches("remove") {
let path = matches.get_one::<String>("path").expect("No path passed"); let path = matches.get_one::<String>("path").expect("No path passed");
afc_client.remove(path).await.expect("Failed to remove"); afc_client.remove(path).await.expect("Failed to remove");