img_archive_parser/
lib.rs

1use byteorder::{LittleEndian, ReadBytesExt};
2use std::io::{Read, Seek, SeekFrom, Write};
3
4const IMG_HEADER: u32 = 0x32524556;
5const ENTRY_SIZE: u64 = 0x20;
6const SECTOR_SIZE: u32 = 0x800;
7
8#[repr(C)]
9#[derive(Debug)]
10pub struct Entry {
11    pub offset: u32,
12    pub streaming_size: u16,
13    pub archive_size: u16,
14    pub file_name: [u8; 24], // This should be parsed as a null-terminated string
15}
16
17#[derive(Debug)]
18pub struct IMGArchive<B: Read + Seek> {
19    buffer: B,
20    entry_count: u32,
21}
22
23impl<B: Read + Seek> IMGArchive<B> {
24    pub fn new(mut buffer: B) -> Result<Self, Box<dyn std::error::Error>> {
25        let header = buffer.read_u32::<LittleEndian>()?;
26        if header != IMG_HEADER {
27            return Err("invalid .img file".into());
28        }
29        let entry_count = buffer.read_u32::<LittleEndian>()?;
30        Ok(IMGArchive {
31            buffer,
32            entry_count,
33        })
34    }
35
36    pub fn extract<W: Write>(&mut self, entry: &Entry, writer: &mut W) -> std::io::Result<()> {
37        let current_pos = self.buffer.stream_position()?;
38
39        // Every file contained in the img archive must be sector aligned,
40        // where the size of each sector is 2048 bytes.
41        // Thus values for offset and size have to be multiplied by 2048.
42        let offset = (entry.offset as u64) * SECTOR_SIZE as u64;
43        let size = (entry.streaming_size as u32) * SECTOR_SIZE;
44
45        self.buffer.seek(SeekFrom::Start(offset))?;
46        let mut buffer = vec![0; size as usize];
47        self.buffer.read_exact(&mut buffer)?;
48        writer.write_all(&buffer)?;
49
50        // Seek back to the stored position
51        self.buffer.seek(SeekFrom::Start(current_pos))?;
52
53        Ok(())
54    }
55}
56
57impl<B: Read + Seek> Iterator for IMGArchive<B> {
58    type Item = Result<Entry, Box<dyn std::error::Error>>;
59
60    fn next(&mut self) -> Option<Self::Item> {
61        let current_pos = match self.buffer.stream_position() {
62            Ok(pos) => pos,
63            Err(e) => return Some(Err(e.into())),
64        };
65        if (current_pos / ENTRY_SIZE) > self.entry_count as u64 {
66            return None;
67        }
68
69        let offset = match self.buffer.read_u32::<LittleEndian>() {
70            Ok(offset) => offset,
71            Err(e) => return Some(Err(e.into())),
72        };
73        let streaming_size = match self.buffer.read_u16::<LittleEndian>() {
74            Ok(size) => size,
75            Err(e) => return Some(Err(e.into())),
76        };
77        let archive_size = match self.buffer.read_u16::<LittleEndian>() {
78            Ok(size) => size,
79            Err(e) => return Some(Err(e.into())),
80        };
81
82        let mut file_name = [0u8; 24];
83        match self.buffer.read_exact(&mut file_name) {
84            Ok(_) => (),
85            Err(e) => return Some(Err(e.into())),
86        };
87
88        Some(Ok(Entry {
89            offset,
90            streaming_size,
91            archive_size,
92            file_name,
93        }))
94    }
95}