mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +01:00
afc pull files
This commit is contained in:
76
idevice/src/afc/file.rs
Normal file
76
idevice/src/afc/file.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user