use async_zip::base::read::seek::ZipFileReader;
use futures::AsyncReadExt as _;
use tokio::io::{AsyncBufRead, AsyncSeek, AsyncWriteExt as _};
use crate::{IdeviceError, services::restore::RestoreError};
pub struct Ipsw<R>
where
R: AsyncBufRead + AsyncSeek + Unpin,
{
zip: async_zip::tokio::read::seek::ZipFileReader<R>,
}
impl<R> std::fmt::Debug for Ipsw<R>
where
R: AsyncBufRead + AsyncSeek + Unpin,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Ipsw").finish_non_exhaustive()
}
}
impl<R> Ipsw<R>
where
R: AsyncBufRead + AsyncSeek + Unpin,
{
pub async fn new(reader: R) -> Result<Self, IdeviceError> {
let zip = ZipFileReader::with_tokio(reader).await.map_err(|e| {
IdeviceError::Restore(RestoreError::Ipsw(format!("failed to open IPSW: {e}")))
})?;
Ok(Self { zip })
}
fn entry_index(&self, path: &str) -> Option<usize> {
self.zip
.file()
.entries()
.iter()
.position(|e| e.filename().as_str().map(|f| f == path).unwrap_or(false))
}
pub async fn read_file(&mut self, path: &str) -> Result<Vec<u8>, IdeviceError> {
let idx = self.entry_index(path).ok_or_else(|| {
IdeviceError::Restore(RestoreError::Ipsw(format!(
"entry `{path}` not found in IPSW"
)))
})?;
let mut reader = self.zip.reader_with_entry(idx).await.map_err(|e| {
IdeviceError::Restore(RestoreError::Ipsw(format!(
"failed to open entry `{path}`: {e}"
)))
})?;
let mut buf = Vec::new();
reader.read_to_end(&mut buf).await.map_err(|e| {
IdeviceError::Restore(RestoreError::Ipsw(format!(
"failed to read entry `{path}`: {e}"
)))
})?;
Ok(buf)
}
pub async fn extract_to_writer<W>(
&mut self,
path: &str,
writer: &mut W,
) -> Result<(), IdeviceError>
where
W: tokio::io::AsyncWrite + Unpin,
{
let idx = self.entry_index(path).ok_or_else(|| {
IdeviceError::Restore(RestoreError::Ipsw(format!(
"entry `{path}` not found in IPSW"
)))
})?;
let mut reader = self.zip.reader_with_entry(idx).await.map_err(|e| {
IdeviceError::Restore(RestoreError::Ipsw(format!(
"failed to open entry `{path}`: {e}"
)))
})?;
let mut buf = vec![0u8; 1 << 20];
loop {
let n = reader.read(&mut buf).await.map_err(|e| {
IdeviceError::Restore(RestoreError::Ipsw(format!("read `{path}`: {e}")))
})?;
if n == 0 {
break;
}
writer.write_all(&buf[..n]).await.map_err(|e| {
IdeviceError::Restore(RestoreError::Ipsw(format!("write `{path}`: {e}")))
})?;
}
writer.flush().await.map_err(|e| {
IdeviceError::Restore(RestoreError::Ipsw(format!("flush `{path}`: {e}")))
})?;
Ok(())
}
pub(crate) async fn open_entry_reader<'a>(
&'a mut self,
path: &str,
) -> Result<Box<dyn super::state_machine::ComponentReader + Send + 'a>, IdeviceError>
where
R: Send,
{
let idx = self.entry_index(path).ok_or_else(|| {
IdeviceError::Restore(RestoreError::Ipsw(format!(
"entry `{path}` not found in IPSW"
)))
})?;
let reader = self.zip.reader_with_entry(idx).await.map_err(|e| {
IdeviceError::Restore(RestoreError::Ipsw(format!(
"failed to open entry `{path}`: {e}"
)))
})?;
Ok(Box::new(EntryReader { inner: reader }))
}
pub async fn build_manifest(&mut self) -> Result<plist::Dictionary, IdeviceError> {
let bytes = self.read_file("BuildManifest.plist").await?;
match plist::from_bytes::<plist::Value>(&bytes)? {
plist::Value::Dictionary(d) => Ok(d),
_ => Err(IdeviceError::Restore(RestoreError::Ipsw(
"BuildManifest.plist is not a dictionary".into(),
))),
}
}
pub async fn read_component(
&mut self,
build_identity: &plist::Dictionary,
component: &str,
) -> Result<Vec<u8>, IdeviceError> {
let path = component_path(build_identity, component)?;
self.read_file(&path).await
}
}
struct EntryReader<T> {
inner: T,
}
impl<T> super::state_machine::ComponentReader for EntryReader<T>
where
T: futures::AsyncRead + Unpin + Send,
{
fn read<'a>(
&'a mut self,
buf: &'a mut [u8],
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<usize, IdeviceError>> + Send + 'a>>
{
Box::pin(async move {
self.inner.read(buf).await.map_err(|e| {
IdeviceError::Restore(RestoreError::Ipsw(format!("read archive entry: {e}")))
})
})
}
}
pub fn component_path(
build_identity: &plist::Dictionary,
component: &str,
) -> Result<String, IdeviceError> {
build_identity
.get("Manifest")
.and_then(|m| m.as_dictionary())
.and_then(|m| m.get(component))
.and_then(|c| c.as_dictionary())
.and_then(|c| c.get("Info"))
.and_then(|i| i.as_dictionary())
.and_then(|i| i.get("Path"))
.and_then(|p| p.as_string())
.map(|s| s.to_string())
.ok_or_else(|| {
IdeviceError::Restore(RestoreError::ComponentNotFound(component.to_string()))
})
}
pub fn components_loaded_by_iboot(build_identity: &plist::Dictionary) -> Vec<String> {
let mut out = Vec::new();
let Some(manifest) = build_identity
.get("Manifest")
.and_then(|m| m.as_dictionary())
else {
return out;
};
for (name, node) in manifest {
let Some(info) = node
.as_dictionary()
.and_then(|n| n.get("Info"))
.and_then(|i| i.as_dictionary())
else {
continue;
};
let iboot = info
.get("IsLoadedByiBoot")
.and_then(|v| v.as_boolean())
.unwrap_or(false);
let stage1 = info
.get("IsLoadedByiBootStage1")
.and_then(|v| v.as_boolean())
.unwrap_or(false);
if iboot && !stage1 {
out.push(name.clone());
}
}
out
}