1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
//! A library for reading data from the header of a .swf file that can also be
//! used as the ground-works for a larger library that works on parsing the
//! rest.
//!
//! * [Github repo](https://github.com/iirelu/swf-headers)
//! * [Crates.io](https://crates.io/crates/swf-headers)
//!
//! Parsing SWF files can be tricky, which is why I made this crate to help
//! you out! Not only does it provide a convenient way to read through the
//! headers of any SWF file, but it also gives you a readable stream of
//! everything just after the header, with all compression issues sorted
//! out for you! How useful.

#![warn(missing_docs)]

extern crate byteorder;
extern crate flate2;
extern crate lzma;
extern crate bit_range;

mod decoded_swf;
mod error;

use std::fs::File;
use std::path::Path;

pub use decoded_swf::DecodedSwf;
pub use error::Error;

use byteorder::{LittleEndian, ReadBytesExt};
use bit_range::BitRange;

/// An enum representing all the valid signatures of a SWF file.
///
/// As of the latest SWF specification, there are 3 valid signatures a SWF file
/// can have. The first three bytes of a SWF file act as the magic numbers, FWS
/// (SWF backwards) was defined with the original specification, and designates
/// an uncompressed SWF file. CWS was introduced with SWF 6, and specifies that
/// all bytes beyond the first 8 are compressed using zlib. ZWS was added with
/// SWF 13, and displays the same concept, but with LZMA instead of zlib.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Signature {
    /// A signature of FWS, meaning an uncompressed SWF file.
    Uncompressed,
    /// A signature of CWS, meaning a zlib-compressed SWF file.
    ZlibCompressed,
    /// A signature of ZWS, meaning an LZMA-compressed SWF file.
    LzmaCompressed
}

/// The primary struct, managing all the parsing and storage of SWF header
/// information.
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct SwfHeaders {
    signature: Signature,
    version: u8,
    file_length: u32,
    width: u32,
    height: u32,
    frame_rate: u16,
    frame_count: u16
}

impl SwfHeaders {
    /// Wraps over read_from(), taking a path and opening it for you.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use swf_headers::SwfHeaders;
    /// if let Ok((headers, decoded)) = SwfHeaders::open("example.swf") {
    ///     // ...
    /// }
    pub fn open<T: AsRef<Path>>(path: T) -> Result<(Self, DecodedSwf), Error> {
        Self::read_from(try!(File::open(path)))
    }

    /// Takes a SWF file and parses its headers, returning the header struct
    /// along with a readable DecodedSwf if you wish to continue parsing the
    /// file.
    ///
    /// The vast bulk of SWF parsing happens in here. The code is documented,
    /// so you can read through the source if you want to understand how it
    /// all works.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use std::fs::File;
    /// use swf_headers::SwfHeaders;
    /// if let Ok(file) = File::open("example.swf") {
    ///     let (headers, decoded) = SwfHeaders::read_from(file).unwrap();
    ///     // ...
    /// }
    /// ```
    pub fn read_from(mut file: File) -> Result<(Self, DecodedSwf), Error> {
        // SWF header strcture overview:
        // Everything is little endian.
        //
        // Signature: u8. Either 'F', 'C', or 'Z' for uncompressed, zlib, or LZMA respectively
        // Magic number: u8. Always 0x57 ('W')
        // Magic number: u8. Always 0x53 ('S')
        // Version: u8
        // File length: u32.
        // Frame size: ???. Here be monsters. A variable-length RECT as defined by the spec
        // Framerate: Says its u16, which is a lie as it's actually an 8.8 fixed point value
        // Frame count: u16

        // Get the signature
        let sig = match try!(file.read_u8()) as char {
            'F' => Signature::Uncompressed,
            'C' => Signature::ZlibCompressed,
            'Z' => Signature::LzmaCompressed,
            _ => return Err(Error::NotSwf)
        };

        // Verify that the magic numbers are correct
        match (try!(file.read_u8()), try!(file.read_u8())) {
            (0x57, 0x53) => {},
            _ => return Err(Error::NotSwf)
        }

        // Get the version
        let version = try!(file.read_u8());
        // Get the file length
        let file_length = try!(file.read_u32::<LittleEndian>());

        // From this point on (the 8th byte), the rest of the file will be likely compressed, so
        // we have to work with a decoded copy.
        let mut decoded = try!(DecodedSwf::decompress(file, sig));

        // The logic for this is painful, so it'll be in its own function.
        let (width, height) = try!(parse_rect(&mut decoded));

        // The frame rate is stored in the header as a fixed-point number. Unless it turns out that
        // decimal points in frame rates are common, we won't bother dealing with it.
        let frame_rate_lower = try!(decoded.read_u8());
        let frame_rate_upper = try!(decoded.read_u8());
        if frame_rate_lower != 0 {
            panic!("swf_headers: Decimal points in frame rates not yet supported");
        }
        let frame_rate = frame_rate_upper as u16;

        let frame_count = try!(decoded.read_u16::<LittleEndian>());

        Ok((SwfHeaders {
            signature: sig,
            version: version,
            file_length: file_length,
            width: width,
            height: height,
            frame_rate: frame_rate,
            frame_count: frame_count
        }, decoded))
    }
    /// Returns the signature as an enum representing all valid values.
    pub fn signature(&self) -> Signature {
        self.signature
    }
    /// Returns the version number.
    pub fn version(&self) -> u8 {
        self.version
    }
    /// Returns the uncompressed total file length in bytes.
    pub fn file_length(&self) -> u32 {
        self.file_length
    }
    /// Returns the dimensions in twips (the measurement unit flash uses, 1/20th of a pixel).
    pub fn dimensions_twips(&self) -> (u32, u32) {
        (self.width, self.height)
    }
    /// Returns the dimensions in pixels (converted from twips, sometimes losing accuracy).
    pub fn dimensions(&self) -> (u32, u32) {
        (self.width / 20, self.height / 20)
    }
    /// Returns the frame rate (note: does not yet handle fractional framerates).
    pub fn frame_rate(&self) -> u16 {
        self.frame_rate
    }
    /// Returns the frame count.
    pub fn frame_count(&self) -> u16 {
        self.frame_count
    }
}

fn parse_rect<T: ReadBytesExt>(file: &mut T) -> Result<(u32, u32), Error> {
    let first_byte = try!(file.read_u8());
    let nbits = ((first_byte >> 3) & 0b0001_1111) as u32;
    let nbytes = (5 + nbits * 4) / 8; // ?

    let mut bytes = Vec::new();
    bytes.push(first_byte);

    for _ in 0..nbytes {
        bytes.push(try!(file.read_u8()));
    }

    let width = bytes.get_bit_range(5+nbits..5+nbits*2);
    let height = bytes.get_bit_range(5+nbits*3..5+nbits*4);

    Ok((width, height))
}


#[cfg(test)]
mod tests {
    use super::*;

    // See tests/README.md for more information about these tests

    #[test]
    fn test_245() {
        let (headers, _) = SwfHeaders::open("tests/245.swf").unwrap();
        assert_eq!(headers.signature(), Signature::ZlibCompressed);
        assert_eq!(headers.version(), 9);
        assert_eq!(headers.file_length(), 849486);
        assert_eq!(headers.dimensions_twips(), (6000, 6000));
        assert_eq!(headers.dimensions(), (300, 300));
        assert_eq!(headers.frame_rate(), 30);
        assert_eq!(headers.frame_count(), 1);
    }

    #[test]
    fn test_902() {
        let (headers, _) = SwfHeaders::open("tests/902.swf").unwrap();
        assert_eq!(headers.signature(), Signature::ZlibCompressed);
        assert_eq!(headers.version(), 9);
        assert_eq!(headers.file_length(), 2032206);
        assert_eq!(headers.dimensions_twips(), (6000, 6000));
        assert_eq!(headers.dimensions(), (300, 300));
        assert_eq!(headers.frame_rate(), 30);
        assert_eq!(headers.frame_count(), 1);
    }

    #[test]
    fn test_submachine_1() {
        let (headers, _) = SwfHeaders::open("tests/submachine_1.swf").unwrap();
        assert_eq!(headers.signature(), Signature::ZlibCompressed);
        assert_eq!(headers.version(), 9);
        assert_eq!(headers.file_length(), 1781964);
        assert_eq!(headers.dimensions_twips(), (8000, 8500));
        assert_eq!(headers.dimensions(), (400, 425));
        assert_eq!(headers.frame_rate(), 25);
        assert_eq!(headers.frame_count(), 29);
    }

    #[test]
    fn test_colourshift() {
        let (headers, _) = SwfHeaders::open("tests/colourshift.swf").unwrap();
        assert_eq!(headers.signature(), Signature::ZlibCompressed);
        assert_eq!(headers.version(), 9);
        assert_eq!(headers.file_length(), 189029);
        assert_eq!(headers.dimensions_twips(), (12800, 9600));
        assert_eq!(headers.dimensions(), (640, 480));
        assert_eq!(headers.frame_rate(), 30);
        assert_eq!(headers.frame_count(), 1);
    }
}