swf_headers/
lib.rs

1//! A library for reading data from the header of a .swf file that can also be
2//! used as the ground-works for a larger library that works on parsing the
3//! rest.
4//!
5//! * [Github repo](https://github.com/iirelu/swf-headers)
6//! * [Crates.io](https://crates.io/crates/swf-headers)
7//!
8//! Parsing SWF files can be tricky, which is why I made this crate to help
9//! you out! Not only does it provide a convenient way to read through the
10//! headers of any SWF file, but it also gives you a readable stream of
11//! everything just after the header, with all compression issues sorted
12//! out for you! How useful.
13
14#![warn(missing_docs)]
15
16extern crate byteorder;
17extern crate flate2;
18extern crate lzma;
19extern crate bit_range;
20
21mod decoded_swf;
22mod error;
23
24use std::fs::File;
25use std::path::Path;
26
27pub use decoded_swf::DecodedSwf;
28pub use error::Error;
29
30use byteorder::{LittleEndian, ReadBytesExt};
31use bit_range::BitRange;
32
33/// An enum representing all the valid signatures of a SWF file.
34///
35/// As of the latest SWF specification, there are 3 valid signatures a SWF file
36/// can have. The first three bytes of a SWF file act as the magic numbers, FWS
37/// (SWF backwards) was defined with the original specification, and designates
38/// an uncompressed SWF file. CWS was introduced with SWF 6, and specifies that
39/// all bytes beyond the first 8 are compressed using zlib. ZWS was added with
40/// SWF 13, and displays the same concept, but with LZMA instead of zlib.
41#[derive(Copy, Clone, PartialEq, Debug)]
42pub enum Signature {
43    /// A signature of FWS, meaning an uncompressed SWF file.
44    Uncompressed,
45    /// A signature of CWS, meaning a zlib-compressed SWF file.
46    ZlibCompressed,
47    /// A signature of ZWS, meaning an LZMA-compressed SWF file.
48    LzmaCompressed
49}
50
51/// The primary struct, managing all the parsing and storage of SWF header
52/// information.
53#[derive(Copy, Clone, PartialEq, Debug)]
54pub struct SwfHeaders {
55    signature: Signature,
56    version: u8,
57    file_length: u32,
58    width: u32,
59    height: u32,
60    frame_rate: u16,
61    frame_count: u16
62}
63
64impl SwfHeaders {
65    /// Wraps over read_from(), taking a path and opening it for you.
66    ///
67    /// # Examples
68    ///
69    /// ```rust
70    /// use swf_headers::SwfHeaders;
71    /// if let Ok((headers, decoded)) = SwfHeaders::open("example.swf") {
72    ///     // ...
73    /// }
74    pub fn open<T: AsRef<Path>>(path: T) -> Result<(Self, DecodedSwf), Error> {
75        Self::read_from(try!(File::open(path)))
76    }
77
78    /// Takes a SWF file and parses its headers, returning the header struct
79    /// along with a readable DecodedSwf if you wish to continue parsing the
80    /// file.
81    ///
82    /// The vast bulk of SWF parsing happens in here. The code is documented,
83    /// so you can read through the source if you want to understand how it
84    /// all works.
85    ///
86    /// # Examples
87    ///
88    /// ```rust
89    /// use std::fs::File;
90    /// use swf_headers::SwfHeaders;
91    /// if let Ok(file) = File::open("example.swf") {
92    ///     let (headers, decoded) = SwfHeaders::read_from(file).unwrap();
93    ///     // ...
94    /// }
95    /// ```
96    pub fn read_from(mut file: File) -> Result<(Self, DecodedSwf), Error> {
97        // SWF header strcture overview:
98        // Everything is little endian.
99        //
100        // Signature: u8. Either 'F', 'C', or 'Z' for uncompressed, zlib, or LZMA respectively
101        // Magic number: u8. Always 0x57 ('W')
102        // Magic number: u8. Always 0x53 ('S')
103        // Version: u8
104        // File length: u32.
105        // Frame size: ???. Here be monsters. A variable-length RECT as defined by the spec
106        // Framerate: Says its u16, which is a lie as it's actually an 8.8 fixed point value
107        // Frame count: u16
108
109        // Get the signature
110        let sig = match try!(file.read_u8()) as char {
111            'F' => Signature::Uncompressed,
112            'C' => Signature::ZlibCompressed,
113            'Z' => Signature::LzmaCompressed,
114            _ => return Err(Error::NotSwf)
115        };
116
117        // Verify that the magic numbers are correct
118        match (try!(file.read_u8()), try!(file.read_u8())) {
119            (0x57, 0x53) => {},
120            _ => return Err(Error::NotSwf)
121        }
122
123        // Get the version
124        let version = try!(file.read_u8());
125        // Get the file length
126        let file_length = try!(file.read_u32::<LittleEndian>());
127
128        // From this point on (the 8th byte), the rest of the file will be likely compressed, so
129        // we have to work with a decoded copy.
130        let mut decoded = try!(DecodedSwf::decompress(file, sig));
131
132        // The logic for this is painful, so it'll be in its own function.
133        let (width, height) = try!(parse_rect(&mut decoded));
134
135        // The frame rate is stored in the header as a fixed-point number. Unless it turns out that
136        // decimal points in frame rates are common, we won't bother dealing with it.
137        let frame_rate_lower = try!(decoded.read_u8());
138        let frame_rate_upper = try!(decoded.read_u8());
139        if frame_rate_lower != 0 {
140            panic!("swf_headers: Decimal points in frame rates not yet supported");
141        }
142        let frame_rate = frame_rate_upper as u16;
143
144        let frame_count = try!(decoded.read_u16::<LittleEndian>());
145
146        Ok((SwfHeaders {
147            signature: sig,
148            version: version,
149            file_length: file_length,
150            width: width,
151            height: height,
152            frame_rate: frame_rate,
153            frame_count: frame_count
154        }, decoded))
155    }
156    /// Returns the signature as an enum representing all valid values.
157    pub fn signature(&self) -> Signature {
158        self.signature
159    }
160    /// Returns the version number.
161    pub fn version(&self) -> u8 {
162        self.version
163    }
164    /// Returns the uncompressed total file length in bytes.
165    pub fn file_length(&self) -> u32 {
166        self.file_length
167    }
168    /// Returns the dimensions in twips (the measurement unit flash uses, 1/20th of a pixel).
169    pub fn dimensions_twips(&self) -> (u32, u32) {
170        (self.width, self.height)
171    }
172    /// Returns the dimensions in pixels (converted from twips, sometimes losing accuracy).
173    pub fn dimensions(&self) -> (u32, u32) {
174        (self.width / 20, self.height / 20)
175    }
176    /// Returns the frame rate (note: does not yet handle fractional framerates).
177    pub fn frame_rate(&self) -> u16 {
178        self.frame_rate
179    }
180    /// Returns the frame count.
181    pub fn frame_count(&self) -> u16 {
182        self.frame_count
183    }
184}
185
186fn parse_rect<T: ReadBytesExt>(file: &mut T) -> Result<(u32, u32), Error> {
187    let first_byte = try!(file.read_u8());
188    let nbits = ((first_byte >> 3) & 0b0001_1111) as u32;
189    let nbytes = (5 + nbits * 4) / 8; // ?
190
191    let mut bytes = Vec::new();
192    bytes.push(first_byte);
193
194    for _ in 0..nbytes {
195        bytes.push(try!(file.read_u8()));
196    }
197
198    let width = bytes.get_bit_range(5+nbits..5+nbits*2);
199    let height = bytes.get_bit_range(5+nbits*3..5+nbits*4);
200
201    Ok((width, height))
202}
203
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    // See tests/README.md for more information about these tests
210
211    #[test]
212    fn test_245() {
213        let (headers, _) = SwfHeaders::open("tests/245.swf").unwrap();
214        assert_eq!(headers.signature(), Signature::ZlibCompressed);
215        assert_eq!(headers.version(), 9);
216        assert_eq!(headers.file_length(), 849486);
217        assert_eq!(headers.dimensions_twips(), (6000, 6000));
218        assert_eq!(headers.dimensions(), (300, 300));
219        assert_eq!(headers.frame_rate(), 30);
220        assert_eq!(headers.frame_count(), 1);
221    }
222
223    #[test]
224    fn test_902() {
225        let (headers, _) = SwfHeaders::open("tests/902.swf").unwrap();
226        assert_eq!(headers.signature(), Signature::ZlibCompressed);
227        assert_eq!(headers.version(), 9);
228        assert_eq!(headers.file_length(), 2032206);
229        assert_eq!(headers.dimensions_twips(), (6000, 6000));
230        assert_eq!(headers.dimensions(), (300, 300));
231        assert_eq!(headers.frame_rate(), 30);
232        assert_eq!(headers.frame_count(), 1);
233    }
234
235    #[test]
236    fn test_submachine_1() {
237        let (headers, _) = SwfHeaders::open("tests/submachine_1.swf").unwrap();
238        assert_eq!(headers.signature(), Signature::ZlibCompressed);
239        assert_eq!(headers.version(), 9);
240        assert_eq!(headers.file_length(), 1781964);
241        assert_eq!(headers.dimensions_twips(), (8000, 8500));
242        assert_eq!(headers.dimensions(), (400, 425));
243        assert_eq!(headers.frame_rate(), 25);
244        assert_eq!(headers.frame_count(), 29);
245    }
246
247    #[test]
248    fn test_colourshift() {
249        let (headers, _) = SwfHeaders::open("tests/colourshift.swf").unwrap();
250        assert_eq!(headers.signature(), Signature::ZlibCompressed);
251        assert_eq!(headers.version(), 9);
252        assert_eq!(headers.file_length(), 189029);
253        assert_eq!(headers.dimensions_twips(), (12800, 9600));
254        assert_eq!(headers.dimensions(), (640, 480));
255        assert_eq!(headers.frame_rate(), 30);
256        assert_eq!(headers.frame_count(), 1);
257    }
258}