use std::{ io::{copy, Read, Seek, SeekFrom, Write}, path::{Path, PathBuf}, }; use android_sparse_image::{ split::split_image, ChunkHeader, ChunkHeaderBytes, FileHeader, FileHeaderBytes, CHUNK_HEADER_BYTES_LEN, FILE_HEADER_BYTES_LEN, }; use anyhow::Context; use clap::Parser; #[derive(clap::Parser)] enum Opts { /// Inspect the contents of a sparse image Inspect { img: PathBuf }, /// Expand the content of to Expand { img: PathBuf, out: PathBuf }, /// split content of to fit maximum download size Split { img: PathBuf, size: u32, out: PathBuf, }, } fn inspect(img: &Path) -> anyhow::Result<()> { let mut file = std::fs::File::open(img)?; let mut header_bytes = FileHeaderBytes::default(); file.read_exact(&mut header_bytes)?; let header = FileHeader::from_bytes(&header_bytes)?; println!( "Chunks {}, Expanded size: {} ({} blocks, {} blocksize), checksum: {}:", header.chunks, header.total_size(), header.blocks, header.block_size, header.checksum ); let mut offset: usize = 0; for index in 0..header.chunks { let mut chunk_bytes = ChunkHeaderBytes::default(); file.read_exact(&mut chunk_bytes)?; let chunk = ChunkHeader::from_bytes(&chunk_bytes)?; let out_size = chunk.out_size(&header); match chunk.chunk_type { android_sparse_image::ChunkType::Raw => { println!("{index}: Offset: {offset} - Copying {out_size} bytes"); file.seek(std::io::SeekFrom::Current(chunk.data_size().try_into()?))?; } android_sparse_image::ChunkType::Fill => { let mut fill = [0u8; 4]; file.read_exact(&mut fill)?; println!("{index}: Offset: {offset} - Filling {out_size} bytes with {fill:x?}"); } android_sparse_image::ChunkType::DontCare => { println!("{index}: Offset: {offset} - Skipping {out_size} bytes"); } android_sparse_image::ChunkType::Crc32 => { let mut crc = [0u8; 4]; file.read_exact(&mut crc)?; println!("{index}: CRC value: {:x?}", crc); } } offset += out_size; } Ok(()) } fn expand(img: &Path, out: &Path) -> anyhow::Result<()> { let mut file = std::fs::File::open(img)?; let mut output = std::fs::OpenOptions::new() .create(true) .truncate(false) .write(true) .open(out)?; let mut header_bytes: FileHeaderBytes = [0; FILE_HEADER_BYTES_LEN]; file.read_exact(&mut header_bytes)?; let header = FileHeader::from_bytes(&header_bytes)?; for _ in 0..header.chunks { let mut chunk_bytes: ChunkHeaderBytes = [0; CHUNK_HEADER_BYTES_LEN]; file.read_exact(&mut chunk_bytes)?; let chunk = ChunkHeader::from_bytes(&chunk_bytes)?; let out_size = chunk.out_size(&header); match chunk.chunk_type { android_sparse_image::ChunkType::Raw => { let mut raw = (&mut file).take(out_size.try_into().unwrap()); copy(&mut raw, &mut output)?; } android_sparse_image::ChunkType::Fill => { let mut fill = [0u8; 4]; file.read_exact(&mut fill)?; for _ in 0..out_size / 4 { output.write_all(&fill)?; } } android_sparse_image::ChunkType::DontCare => { output.seek(SeekFrom::Current(out_size.try_into().unwrap()))?; } android_sparse_image::ChunkType::Crc32 => { println!("Ignoring CRC"); } } } output.flush()?; Ok(()) } fn split(img: &Path, size: u32, out: &Path) -> anyhow::Result<()> { let mut file = std::fs::File::open(img)?; let mut header_bytes: FileHeaderBytes = [0; FILE_HEADER_BYTES_LEN]; file.read_exact(&mut header_bytes)?; // Scan all chunks let header = FileHeader::from_bytes(&header_bytes)?; let mut chunks = vec![]; for _ in 0..header.chunks { let mut chunk_bytes: ChunkHeaderBytes = [0; CHUNK_HEADER_BYTES_LEN]; file.read_exact(&mut chunk_bytes)?; let chunk = ChunkHeader::from_bytes(&chunk_bytes)?; file.seek(SeekFrom::Current(chunk.data_size() as i64))?; chunks.push(chunk); } let splits = split_image(&header, &chunks, size)?; for (i, split) in splits.iter().enumerate() { let mut out = out.as_os_str().to_os_string(); out.push(format!(".{i}")); let mut out = std::fs::File::create(&out).with_context(|| format!("Failed to create {out:?}"))?; out.write_all(&split.header.to_bytes())?; for chunk in &split.chunks { out.write_all(&chunk.header.to_bytes())?; file.seek(SeekFrom::Start(chunk.offset as u64)) .context("Failed to seek input file")?; std::io::copy(&mut (&mut file).take(chunk.size as u64), &mut out)?; } } Ok(()) } fn main() -> anyhow::Result<()> { let opts = Opts::parse(); match opts { Opts::Inspect { img } => inspect(&img)?, Opts::Expand { img, out } => expand(&img, &out)?, Opts::Split { img, size, out } => split(&img, size, &out)?, } Ok(()) }