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,
swf_data: Vec<u8>,
) -> 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.
let mut data = Vec::new();
reader.get_mut().read_to_end(&mut data)?;
let mut data = Vec::with_capacity(swf_stream.uncompressed_length);
// 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();
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-traits = "0.2"
libflate = {version = "0.1", optional = true}
log = "0.4"
flate2 = {version = "1.0", optional = true}
xz2 = {version = "0.1.5", optional = true}

View File

@ -8,6 +8,7 @@ use crate::error::{Error, Result};
use crate::types::*;
use byteorder::{LittleEndian, ReadBytesExt};
use std::collections::HashSet;
use std::convert::TryInto;
use std::io::{self, Read};
/// Convenience method to parse an SWF.
@ -23,12 +24,25 @@ use std::io::{self, Read};
/// println!("Number of frames: {}", swf.header.num_frames);
/// ```
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.
let mut data = Vec::new();
let version = reader.version;
reader.get_mut().read_to_end(&mut data)?;
let mut data = Vec::with_capacity(swf_stream.uncompressed_length);
let version = header.version;
// 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);
Ok(Swf {
@ -45,22 +59,36 @@ pub fn read_swf<R: Read>(input: R) -> Result<Swf> {
/// # Example
/// ```
/// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap();
/// let (header, _reader) = swf::read_swf_header(&data[..]).unwrap();
/// println!("FPS: {}", header.frame_rate);
/// let swf_stream = swf::read_swf_header(&data[..]).unwrap();
/// println!("FPS: {}", swf_stream.header.frame_rate);
/// ```
pub fn read_swf_header<'a, R: Read + 'a>(
mut input: R,
) -> Result<(Header, Reader<Box<dyn Read + 'a>>)> {
pub fn read_swf_header<'a, R: Read + 'a>(mut input: R) -> Result<SwfStream<'a>> {
// Read SWF header.
let compression = Reader::read_compression_type(&mut input)?;
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.
let decompressed_input: Box<dyn Read> = match compression {
Compression::None => Box::new(input),
Compression::Zlib => make_zlib_reader(input)?,
Compression::Lzma => make_lzma_reader(input)?,
Compression::Zlib => {
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);
@ -74,7 +102,11 @@ pub fn read_swf_header<'a, R: Read + 'a>(
frame_rate,
num_frames,
};
Ok((header, reader))
Ok(SwfStream {
header,
uncompressed_length: uncompressed_length.try_into().unwrap(),
reader,
})
}
#[cfg(feature = "flate2")]
@ -277,8 +309,8 @@ impl<R: Read> Reader<R> {
/// # Example
/// ```
/// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap();
/// let (header, mut reader) = swf::read_swf_header(&data[..]).unwrap();
/// while let Ok(tag) = reader.read_tag() {
/// let mut swf_stream = swf::read_swf_header(&data[..]).unwrap();
/// while let Ok(tag) = swf_stream.reader.read_tag() {
/// println!("Tag: {:?}", tag);
/// }
/// ```
@ -2721,7 +2753,8 @@ pub mod tests {
file.read_to_end(&mut data).unwrap();
// 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();
reader.input.read_to_end(&mut data).unwrap();
@ -2729,7 +2762,7 @@ pub mod tests {
loop {
let pos = cursor.position();
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()
};
let tag_header_length = cursor.position() - pos;

View File

@ -13,6 +13,14 @@ pub struct Swf {
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.
///
/// Notably contains the compression format used by the rest of the SWF data.