swf: Try to recover from incorrect zlib streams

Some SWFs are compressed incorrectly, often with incorrect
compressed/uncompressed lengths, causing the zlib decoders
to vomit if you try to decompress them fully. However, often times
the data still decompresses all the way to the End tag, and we
still want to try to play it even if it's corrupt.
Now these errors only omit a warning, and we'll continue to run
the SWF.

Addresses #86.
This commit is contained in:
Mike Welsh 2019-10-10 13:07:07 -07:00
parent 93a221aea4
commit 800147043a
4 changed files with 71 additions and 20 deletions

View File

@ -68,10 +68,19 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
navigator: Navigator, navigator: Navigator,
swf_data: Vec<u8>, swf_data: Vec<u8>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let (header, mut reader) = swf::read::read_swf_header(&swf_data[..]).unwrap(); let swf_stream = swf::read::read_swf_header(&swf_data[..]).unwrap();
let header = swf_stream.header;
let mut reader = swf_stream.reader;
// Decompress the entire SWF in memory. // Decompress the entire SWF in memory.
let mut data = Vec::new(); let mut data = Vec::with_capacity(swf_stream.uncompressed_length);
reader.get_mut().read_to_end(&mut data)?;
// Sometimes SWFs will have an incorrectly compressed stream,
// but will otherwise decompress fine up to the End tag.
// So just warn on this case and try to continue gracefully.
if let Err(e) = reader.get_mut().read_to_end(&mut data) {
log::error!("Error decompressing SWF, may be corrupt: {}", e);
}
let swf_len = data.len(); let swf_len = data.len();
info!("{}x{}", header.stage_size.x_max, header.stage_size.y_max); info!("{}x{}", header.stage_size.x_max, header.stage_size.y_max);

View File

@ -14,6 +14,7 @@ byteorder = "1.0"
num-derive = "0.2" num-derive = "0.2"
num-traits = "0.2" num-traits = "0.2"
libflate = {version = "0.1", optional = true} libflate = {version = "0.1", optional = true}
log = "0.4"
flate2 = {version = "1.0", optional = true} flate2 = {version = "1.0", optional = true}
xz2 = {version = "0.1.5", optional = true} xz2 = {version = "0.1.5", optional = true}

View File

@ -8,6 +8,7 @@ use crate::error::{Error, Result};
use crate::types::*; use crate::types::*;
use byteorder::{LittleEndian, ReadBytesExt}; use byteorder::{LittleEndian, ReadBytesExt};
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryInto;
use std::io::{self, Read}; use std::io::{self, Read};
/// Convenience method to parse an SWF. /// Convenience method to parse an SWF.
@ -23,12 +24,25 @@ use std::io::{self, Read};
/// println!("Number of frames: {}", swf.header.num_frames); /// println!("Number of frames: {}", swf.header.num_frames);
/// ``` /// ```
pub fn read_swf<R: Read>(input: R) -> Result<Swf> { pub fn read_swf<R: Read>(input: R) -> Result<Swf> {
let (header, mut reader) = read_swf_header(input)?; let swf_stream = read_swf_header(input)?;
let header = swf_stream.header;
let mut reader = swf_stream.reader;
// Decompress all of SWF into memory at once. // Decompress all of SWF into memory at once.
let mut data = Vec::new(); let mut data = Vec::with_capacity(swf_stream.uncompressed_length);
let version = reader.version; let version = header.version;
reader.get_mut().read_to_end(&mut data)?;
// Some SWF streams may not be compressed correctly,
// (e.g. incorrect data length in the stream), so decompressing
// may throw an error even though the data otherwise comes
// through the stream.
// We'll still try to parse what we get if the full decompression fails.
if let Err(e) = reader.get_mut().read_to_end(&mut data) {
log::warn!("Error decompressing SWF stream, may be corrupt: {}", e);
}
if data.len() != swf_stream.uncompressed_length {
log::warn!("SWF length doesn't match header, may be corrupt");
}
let mut reader = Reader::new(&data[..], version); let mut reader = Reader::new(&data[..], version);
Ok(Swf { Ok(Swf {
@ -45,22 +59,36 @@ pub fn read_swf<R: Read>(input: R) -> Result<Swf> {
/// # Example /// # Example
/// ``` /// ```
/// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap(); /// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap();
/// let (header, _reader) = swf::read_swf_header(&data[..]).unwrap(); /// let swf_stream = swf::read_swf_header(&data[..]).unwrap();
/// println!("FPS: {}", header.frame_rate); /// println!("FPS: {}", swf_stream.header.frame_rate);
/// ``` /// ```
pub fn read_swf_header<'a, R: Read + 'a>( pub fn read_swf_header<'a, R: Read + 'a>(mut input: R) -> Result<SwfStream<'a>> {
mut input: R,
) -> Result<(Header, Reader<Box<dyn Read + 'a>>)> {
// Read SWF header. // Read SWF header.
let compression = Reader::read_compression_type(&mut input)?; let compression = Reader::read_compression_type(&mut input)?;
let version = input.read_u8()?; let version = input.read_u8()?;
let _uncompressed_length = input.read_u32::<LittleEndian>()?; let uncompressed_length = input.read_u32::<LittleEndian>()?;
// Now the SWF switches to a compressed stream. // Now the SWF switches to a compressed stream.
let decompressed_input: Box<dyn Read> = match compression { let decompressed_input: Box<dyn Read> = match compression {
Compression::None => Box::new(input), Compression::None => Box::new(input),
Compression::Zlib => make_zlib_reader(input)?, Compression::Zlib => {
Compression::Lzma => make_lzma_reader(input)?, if version < 6 {
log::warn!(
"zlib compressed SWF is version {} but minimum version is 6",
version
);
}
make_zlib_reader(input)?
}
Compression::Lzma => {
if version < 13 {
log::warn!(
"LZMA compressed SWF is version {} but minimum version is 13",
version
);
}
make_lzma_reader(input)?
}
}; };
let mut reader = Reader::new(decompressed_input, version); let mut reader = Reader::new(decompressed_input, version);
@ -74,7 +102,11 @@ pub fn read_swf_header<'a, R: Read + 'a>(
frame_rate, frame_rate,
num_frames, num_frames,
}; };
Ok((header, reader)) Ok(SwfStream {
header,
uncompressed_length: uncompressed_length.try_into().unwrap(),
reader,
})
} }
#[cfg(feature = "flate2")] #[cfg(feature = "flate2")]
@ -277,8 +309,8 @@ impl<R: Read> Reader<R> {
/// # Example /// # Example
/// ``` /// ```
/// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap(); /// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap();
/// let (header, mut reader) = swf::read_swf_header(&data[..]).unwrap(); /// let mut swf_stream = swf::read_swf_header(&data[..]).unwrap();
/// while let Ok(tag) = reader.read_tag() { /// while let Ok(tag) = swf_stream.reader.read_tag() {
/// println!("Tag: {:?}", tag); /// println!("Tag: {:?}", tag);
/// } /// }
/// ``` /// ```
@ -2721,7 +2753,8 @@ pub mod tests {
file.read_to_end(&mut data).unwrap(); file.read_to_end(&mut data).unwrap();
// Halfway parse the SWF file until we find the tag we're searching for. // Halfway parse the SWF file until we find the tag we're searching for.
let (swf, mut reader) = super::read_swf_header(&data[..]).unwrap(); let swf_stream = super::read_swf_header(&data[..]).unwrap();
let mut reader = swf_stream.reader;
let mut data = Vec::new(); let mut data = Vec::new();
reader.input.read_to_end(&mut data).unwrap(); reader.input.read_to_end(&mut data).unwrap();
@ -2729,7 +2762,7 @@ pub mod tests {
loop { loop {
let pos = cursor.position(); let pos = cursor.position();
let (swf_tag_code, length) = { let (swf_tag_code, length) = {
let mut tag_reader = Reader::new(&mut cursor, swf.version); let mut tag_reader = Reader::new(&mut cursor, swf_stream.header.version);
tag_reader.read_tag_code_and_length().unwrap() tag_reader.read_tag_code_and_length().unwrap()
}; };
let tag_header_length = cursor.position() - pos; let tag_header_length = cursor.position() - pos;

View File

@ -13,6 +13,14 @@ pub struct Swf {
pub tags: Vec<Tag>, pub tags: Vec<Tag>,
} }
/// Returned by `read::read_swf_header`. Includes the decompress
/// stream as well as the uncompressed data length.
pub struct SwfStream<'a> {
pub header: Header,
pub uncompressed_length: usize,
pub reader: crate::read::Reader<Box<dyn std::io::Read + 'a>>,
}
/// The header of an SWF file. /// The header of an SWF file.
/// ///
/// Notably contains the compression format used by the rest of the SWF data. /// Notably contains the compression format used by the rest of the SWF data.