mugen_sff/
decoder.rs

1use std::{
2    borrow::Cow,
3    error::Error,
4    fmt::Display,
5    io::{self, Cursor, Seek, SeekFrom},
6    str::{self, Utf8Error},
7};
8
9use byteorder::{LittleEndian, ReadBytesExt};
10
11use crate::{PaletteKind, SpriteId, Version};
12
13#[derive(Debug, Clone)]
14pub struct Decoder<'a> {
15    version: Version,
16    groups_count: u32,
17    images_count: u32,
18    palette_kind: PaletteKind,
19    comments: Comments<'a>,
20    sprites: Vec<Sprite<'a>>,
21}
22
23#[derive(Debug)]
24pub enum DecodeError {
25    InvalidData(io::Error),
26    InvalidSignature,
27    UnsuporttedVersion(Version),
28    InvalidPaletteKind,
29    PreviousPaletteNotFound,
30    LinkedSpriteNotFound {
31        sprite_id: SpriteId,
32        linked_index: u16,
33    },
34}
35
36impl Display for DecodeError {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            DecodeError::InvalidData(err) => err.fmt(f),
40            DecodeError::InvalidSignature => write!(f, "invalid signature"),
41            DecodeError::UnsuporttedVersion(v) => write!(f, "unsupported version {:?}", v),
42            DecodeError::InvalidPaletteKind => write!(f, "invalid palette kind"),
43            DecodeError::PreviousPaletteNotFound => write!(f, "previous palette not found"),
44            DecodeError::LinkedSpriteNotFound {
45                sprite_id,
46                linked_index,
47            } => write!(
48                f,
49                "invalid link {} for sprite {}-{}",
50                linked_index, sprite_id.group, sprite_id.image
51            ),
52        }
53    }
54}
55
56impl Error for DecodeError {
57    fn source(&self) -> Option<&(dyn Error + 'static)> {
58        match self {
59            DecodeError::InvalidData(ref err) => Some(err),
60            _ => None,
61        }
62    }
63}
64
65impl From<io::Error> for DecodeError {
66    fn from(error: io::Error) -> Self {
67        DecodeError::InvalidData(error)
68    }
69}
70
71impl<'a> Decoder<'a> {
72    pub fn decode(data: &'a [u8]) -> Result<Self, DecodeError> {
73        if &data[0..12] != b"ElecbyteSpr\0" {
74            return Err(DecodeError::InvalidSignature);
75        }
76
77        let mut bytes = Cursor::new(&data);
78        bytes.set_position(12);
79
80        let version_low3 = bytes.read_u8()?;
81        let version_low2 = bytes.read_u8()?;
82        let version_low1 = bytes.read_u8()?;
83        let version_high = bytes.read_u8()?;
84        let version = Version(version_high, version_low1, version_low2, version_low3);
85        if version_high != 1 {
86            return Err(DecodeError::UnsuporttedVersion(version));
87        }
88
89        let groups_count = bytes.read_u32::<LittleEndian>()?;
90        let images_count = bytes.read_u32::<LittleEndian>()?;
91        let first_subfile_offset = bytes.read_u32::<LittleEndian>()?;
92        let subfile_header_size = bytes.read_u32::<LittleEndian>()?;
93        let palette_kind = match bytes.read_u8()? {
94            0 => PaletteKind::Individual,
95            1 => PaletteKind::Shared,
96            _ => return Err(DecodeError::InvalidPaletteKind),
97        };
98
99        let comments = Comments(&data[36..511]);
100
101        let mut images: Vec<Sprite> = Vec::with_capacity(images_count as usize);
102        let mut next_subfile_offset = Some(first_subfile_offset);
103        let mut previous_palette_offset = None;
104        let total_size = data.len() as u32;
105        while let Some(offset) = next_subfile_offset.take() {
106            bytes.set_position(offset.into());
107
108            next_subfile_offset = bytes.read_u32::<LittleEndian>().ok().and_then(|n| match n {
109                n if n > 0 && n + subfile_header_size <= total_size => Some(n),
110                _ => None,
111            });
112
113            let size = bytes.read_u32::<LittleEndian>()? as usize;
114            let x = bytes.read_i16::<LittleEndian>()?;
115            let y = bytes.read_i16::<LittleEndian>()?;
116            let group = bytes.read_u16::<LittleEndian>()?;
117            let image = bytes.read_u16::<LittleEndian>()?;
118            let linked_index = bytes.read_u16::<LittleEndian>()?;
119            let use_previous_palette = bytes.read_u8()? != 0;
120
121            bytes.seek(SeekFrom::Current(13))?;
122
123            let sprite_id = SpriteId { group, image };
124            let coordinates = Coordinates { x, y };
125
126            let data_offset = bytes.position() as usize;
127            let palette_size = 256 * 3;
128
129            let (data_size, palette_offset) = match previous_palette_offset {
130                None if use_previous_palette => return Err(DecodeError::PreviousPaletteNotFound),
131                Some(offset) if use_previous_palette => (size, offset),
132                _ => {
133                    let offset = (data_offset + size) - palette_size;
134                    previous_palette_offset = Some(offset);
135                    (size - palette_size, offset)
136                }
137            };
138
139            let sprite = if size == 0 {
140                let linked_sprite =
141                    images
142                        .get(linked_index as usize)
143                        .ok_or(DecodeError::LinkedSpriteNotFound {
144                            sprite_id,
145                            linked_index,
146                        })?;
147
148                Sprite {
149                    id: sprite_id,
150                    coordinates,
151                    ..linked_sprite.clone()
152                }
153            } else {
154                Sprite {
155                    id: sprite_id,
156                    data: Cow::Borrowed(&data[data_offset..data_offset + data_size]),
157                    palette: Cow::Borrowed(&data[palette_offset..palette_offset + palette_size]),
158                    coordinates,
159                }
160            };
161
162            images.push(sprite);
163        }
164
165        Ok(Decoder {
166            version,
167            groups_count,
168            images_count,
169            palette_kind,
170            comments,
171            sprites: images,
172        })
173    }
174
175    pub fn comments(&self) -> &Comments {
176        &self.comments
177    }
178
179    pub fn palette_kind(&self) -> PaletteKind {
180        self.palette_kind
181    }
182
183    pub fn version(&self) -> Version {
184        self.version
185    }
186
187    pub fn groups_count(&self) -> u32 {
188        self.groups_count
189    }
190
191    pub fn images_count(&self) -> u32 {
192        self.images_count
193    }
194
195    pub fn sprites(&self) -> impl Iterator<Item = &Sprite> {
196        self.sprites.iter()
197    }
198}
199
200impl<'a> IntoIterator for Decoder<'a> {
201    type Item = Sprite<'a>;
202    type IntoIter = std::vec::IntoIter<Self::Item>;
203
204    fn into_iter(self) -> Self::IntoIter {
205        self.sprites.into_iter()
206    }
207}
208
209#[derive(Debug, Clone, Copy, Eq, PartialEq)]
210pub struct Coordinates {
211    pub x: i16,
212    pub y: i16,
213}
214
215impl From<(i16, i16)> for Coordinates {
216    fn from((x, y): (i16, i16)) -> Self {
217        Self { x, y }
218    }
219}
220
221impl From<Coordinates> for (i16, i16) {
222    fn from(Coordinates { x, y }: Coordinates) -> Self {
223        (x, y)
224    }
225}
226
227#[derive(Debug, Clone)]
228pub struct Sprite<'a> {
229    id: SpriteId,
230    data: Cow<'a, [u8]>,
231    palette: Cow<'a, [u8]>,
232    coordinates: Coordinates,
233}
234
235impl<'a> Sprite<'a> {
236    pub fn id(&self) -> SpriteId {
237        self.id
238    }
239
240    pub fn coordinates(&self) -> Coordinates {
241        self.coordinates
242    }
243
244    pub fn raw_data(&self) -> &[u8] {
245        &self.data
246    }
247
248    pub fn palette(&self) -> &[u8] {
249        &self.palette
250    }
251
252    pub fn to_owned(&self) -> Sprite<'static> {
253        Sprite {
254            id: self.id,
255            data: Cow::Owned(self.raw_data().to_owned()),
256            palette: Cow::Owned(self.palette().to_owned()),
257            coordinates: self.coordinates,
258        }
259    }
260}
261
262#[derive(Debug, Clone, PartialEq, Eq)]
263pub struct Comments<'a>(&'a [u8]);
264
265impl<'a> Comments<'a> {
266    pub fn as_str(&self) -> Result<&str, Utf8Error> {
267        Ok(str::from_utf8(self.0)?.trim_end_matches('\u{0}'))
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use crate::{decoder::Decoder, PaletteKind, Version};
274
275    #[test]
276    fn decode_headers() {
277        let sff = include_bytes!("../tests/samples/sample/sample.sff");
278        let sff = Decoder::decode(sff).unwrap();
279
280        assert_eq!(Version(1, 0, 1, 0), sff.version());
281        assert_eq!(4, sff.groups_count());
282        assert_eq!(8, sff.images_count());
283        assert_eq!(PaletteKind::Shared, sff.palette_kind());
284        assert_eq!(Ok("Some comment"), sff.comments().as_str());
285    }
286
287    #[test]
288    fn decode_sprites() {
289        let sff = include_bytes!("../tests/samples/sample/sample.sff");
290        let sff = Decoder::decode(sff).unwrap();
291
292        let sprites = sff.sprites().collect::<Vec<_>>();
293
294        assert_eq!(8, sprites.len());
295
296        assert_eq!(sprites[0].id, (0, 0).into());
297        assert_eq!(sprites[0].coordinates, (0, 0).into());
298        assert_eq!(
299            [sprites[0].raw_data(), sprites[0].palette()].concat(),
300            include_bytes!("../tests/samples/sample/sample-0-0.pcx")
301        );
302
303        assert_eq!(sprites[1].id, (0, 1).into());
304        assert_eq!(sprites[1].coordinates, (-50, -50).into());
305        assert_eq!(
306            [sprites[1].raw_data(), sprites[1].palette()].concat(),
307            include_bytes!("../tests/samples/sample/sample-0-1.pcx")
308        );
309
310        assert_eq!(sprites[2].id, (1, 0).into());
311        assert_eq!(sprites[2].coordinates, (-50, -50).into());
312        assert_eq!(
313            [sprites[2].raw_data(), sprites[2].palette()].concat(),
314            include_bytes!("../tests/samples/sample/sample-1-0.pcx")
315        );
316
317        assert_eq!(sprites[3].id, (1, 1).into());
318        assert_eq!(sprites[3].coordinates, (0, 0).into());
319        assert_eq!(
320            [sprites[3].raw_data(), sprites[3].palette()].concat(),
321            include_bytes!("../tests/samples/sample/sample-1-1.pcx")
322        );
323
324        assert_eq!(sprites[4].id, (10, 10).into());
325        assert_eq!(sprites[4].coordinates, (0, 0).into());
326        assert_eq!(
327            [sprites[4].raw_data(), sprites[4].palette()].concat(),
328            include_bytes!("../tests/samples/sample/sample-10-10.pcx")
329        );
330
331        assert_eq!(sprites[5].id, (10, 20).into());
332        assert_eq!(sprites[5].coordinates, (-100, -100).into());
333        assert_eq!(
334            [sprites[5].raw_data(), sprites[5].palette()].concat(),
335            include_bytes!("../tests/samples/sample/sample-10-20.pcx")
336        );
337
338        assert_eq!(sprites[6].id, (50, 0).into());
339        assert_eq!(sprites[6].coordinates, (-200, -200).into());
340        assert_eq!(
341            [sprites[6].raw_data(), sprites[6].palette()].concat(),
342            include_bytes!("../tests/samples/sample/sample-50-0.pcx")
343        );
344
345        assert_eq!(sprites[7].id, (50, 5).into());
346        assert_eq!(sprites[7].coordinates, (-200, -200).into());
347        assert_eq!(
348            [sprites[7].raw_data(), sprites[7].palette()].concat(),
349            include_bytes!("../tests/samples/sample/sample-50-5.pcx")
350        );
351    }
352
353    #[test]
354    fn decode_linked_sprites() {
355        let sff = include_bytes!("../tests/samples/sample/linked.sff");
356        let sff = Decoder::decode(sff).unwrap();
357
358        let sprites = sff.sprites().collect::<Vec<_>>();
359
360        assert_eq!(8, sprites.len());
361
362        assert_eq!(sprites[0].id, (0, 0).into());
363        assert_eq!(sprites[0].coordinates, (0, 0).into());
364        assert_eq!(
365            [sprites[0].raw_data(), sprites[0].palette()].concat(),
366            include_bytes!("../tests/samples/sample/linked-0.pcx")
367        );
368
369        assert_eq!(sprites[1].id, (0, 1).into());
370        assert_eq!(sprites[1].coordinates, (-50, -50).into());
371        assert_eq!(
372            [sprites[1].raw_data(), sprites[1].palette()].concat(),
373            include_bytes!("../tests/samples/sample/linked-0.pcx")
374        );
375
376        assert_eq!(sprites[2].id, (1, 0).into());
377        assert_eq!(sprites[2].coordinates, (-50, -50).into());
378        assert_eq!(
379            [sprites[2].raw_data(), sprites[2].palette()].concat(),
380            include_bytes!("../tests/samples/sample/linked-0.pcx")
381        );
382
383        assert_eq!(sprites[3].id, (1, 1).into());
384        assert_eq!(sprites[3].coordinates, (0, 0).into());
385        assert_eq!(
386            [sprites[3].raw_data(), sprites[3].palette()].concat(),
387            include_bytes!("../tests/samples/sample/linked-0.pcx")
388        );
389
390        assert_eq!(sprites[4].id, (10, 10).into());
391        assert_eq!(sprites[4].coordinates, (0, 0).into());
392        assert_eq!(
393            [sprites[4].raw_data(), sprites[4].palette()].concat(),
394            include_bytes!("../tests/samples/sample/linked-1.pcx")
395        );
396
397        assert_eq!(sprites[5].id, (10, 20).into());
398        assert_eq!(sprites[5].coordinates, (-100, -100).into());
399        assert_eq!(
400            [sprites[5].raw_data(), sprites[5].palette()].concat(),
401            include_bytes!("../tests/samples/sample/linked-1.pcx")
402        );
403
404        assert_eq!(sprites[6].id, (50, 0).into());
405        assert_eq!(sprites[6].coordinates, (-200, -200).into());
406        assert_eq!(
407            [sprites[6].raw_data(), sprites[6].palette()].concat(),
408            include_bytes!("../tests/samples/sample/linked-2.pcx")
409        );
410
411        assert_eq!(sprites[7].id, (50, 5).into());
412        assert_eq!(sprites[7].coordinates, (-200, -200).into());
413        assert_eq!(
414            [sprites[7].raw_data(), sprites[7].palette()].concat(),
415            include_bytes!("../tests/samples/sample/linked-2.pcx")
416        );
417    }
418
419    #[test]
420    fn sprite_can_be_owned() {
421        let sff = include_bytes!("../tests/samples/sample/sample.sff");
422        let sff = Decoder::decode(sff).unwrap();
423
424        let sprite = sff.sprites().next().unwrap();
425        let sprite = sprite.to_owned();
426
427        drop(sff);
428
429        assert_eq!(
430            [sprite.raw_data(), sprite.palette()].concat(),
431            include_bytes!("../tests/samples/sample/sample-0-0.pcx")
432        );
433    }
434}