From 1ab5211bfe06c85601e553101b9cca974f3d1fcc Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Fri, 11 Oct 2019 23:02:20 -0700 Subject: [PATCH] swf: Fix compiling with lzma feature --- core/Cargo.toml | 3 +- core/src/player.rs | 21 ++++++++++--- swf/Cargo.toml | 4 +-- swf/src/lib.rs | 2 +- swf/src/read.rs | 78 +++++++++++++++++++++++++++++++++++++--------- swf/src/write.rs | 16 +++++----- 6 files changed, 93 insertions(+), 31 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 26edd6055..ec773aefa 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -26,4 +26,5 @@ default-features = false # can't use rayon on web approx = "0.3.2" [features] -default = ["minimp3"] +default = ["minimp3", "lzma"] +lzma = ["swf/lzma"] diff --git a/core/src/player.rs b/core/src/player.rs index da49d963f..946dbed93 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -71,15 +71,26 @@ impl 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::with_capacity(swf_stream.uncompressed_length); + // Decompress the entire SWF in memory. // 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 data = if header.compression == swf::Compression::Lzma { + // TODO: The LZMA decoder is still funky. + // It always errors, and doesn't return all the data if you use read_to_end, + // but read_exact at least returns the data... why? + // Does the decoder need to be flushed somehow? + let mut data = vec![0u8; swf_stream.uncompressed_length]; + let _ = reader.get_mut().read_exact(&mut data); + data + } else { + let mut data = Vec::with_capacity(swf_stream.uncompressed_length); + if let Err(e) = reader.get_mut().read_to_end(&mut data) { + log::error!("Error decompressing SWF, may be corrupt: {}", e); + } + data + }; let swf_len = data.len(); diff --git a/swf/Cargo.toml b/swf/Cargo.toml index 58407c8a6..ec4101aa0 100644 --- a/swf/Cargo.toml +++ b/swf/Cargo.toml @@ -16,8 +16,8 @@ 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} +xz2 = {version = "0.1.6", optional = true} [features] default = ["libflate"] -lzma-support = ["xz2"] \ No newline at end of file +lzma = ["xz2"] \ No newline at end of file diff --git a/swf/src/lib.rs b/swf/src/lib.rs index 0b65da6f4..6af5703a8 100644 --- a/swf/src/lib.rs +++ b/swf/src/lib.rs @@ -15,7 +15,7 @@ extern crate libflate; #[macro_use] extern crate num_derive; extern crate num_traits; -#[cfg(feature = "lzma-support")] +#[cfg(feature = "lzma")] extern crate xz2; pub mod avm1; diff --git a/swf/src/read.rs b/swf/src/read.rs index fa7574147..5e8c1b9a6 100644 --- a/swf/src/read.rs +++ b/swf/src/read.rs @@ -29,7 +29,21 @@ pub fn read_swf(input: R) -> Result { let mut reader = swf_stream.reader; // Decompress all of SWF into memory at once. - let mut data = Vec::with_capacity(swf_stream.uncompressed_length); + let mut data = if header.compression == Compression::Lzma { + // TODO: The LZMA decoder is still funky. + // It always errors, and doesn't return all the data if you use read_to_end, + // but read_exact at least returns the data... why? + // Does the decoder need to be flushed somehow? + let mut data = vec![0u8; swf_stream.uncompressed_length]; + let _ = reader.get_mut().read_exact(&mut data); + data + } else { + let mut data = Vec::with_capacity(swf_stream.uncompressed_length); + if let Err(e) = reader.get_mut().read_to_end(&mut data) { + log::error!("Error decompressing SWF, may be corrupt: {}", e); + } + data + }; let version = header.version; // Some SWF streams may not be compressed correctly, @@ -66,7 +80,10 @@ pub fn read_swf_header<'a, R: Read + 'a>(mut input: R) -> Result> // Read SWF header. let compression = Reader::read_compression_type(&mut input)?; let version = input.read_u8()?; - let uncompressed_length = input.read_u32::()?; + + // Uncompressed length includes the 4-byte header and 4-byte uncompressed length itself, + // subtract it here. + let uncompressed_length = input.read_u32::()? - 8; // Now the SWF switches to a compressed stream. let decompressed_input: Box = match compression { @@ -87,7 +104,7 @@ pub fn read_swf_header<'a, R: Read + 'a>(mut input: R) -> Result> version ); } - make_lzma_reader(input)? + make_lzma_reader(input, uncompressed_length)? } }; @@ -129,26 +146,57 @@ fn make_zlib_reader<'a, R: Read + 'a>(_input: R) -> Result> { )) } -#[cfg(feature = "lzma-support")] -fn make_lzma_reader<'a, R: Read + 'a>(mut input: R) -> Result> { - // Flash uses a mangled LZMA header, so we have to massage it into the normal - // format. +#[cfg(feature = "lzma")] +fn make_lzma_reader<'a, R: Read + 'a>( + mut input: R, + uncompressed_length: u32, +) -> Result> { use byteorder::WriteBytesExt; use std::io::{Cursor, Write}; - use xz2::stream::{Action, Stream}; - input.read_u32::()?; // Compressed length + use xz2::{ + read::XzDecoder, + stream::{Action, Stream}, + }; + // Flash uses a mangled LZMA header, so we have to massage it into the normal format. + // https://helpx.adobe.com/flash-player/kb/exception-thrown-you-decompress-lzma-compressed.html + // LZMA SWF header: + // Bytes 0..3: ZWS header + // Byte 3: SWF version + // Bytes 4..8: Uncompressed length + // Bytes 8..12: Compressed length + // Bytes 12..17: LZMA properties + // + // LZMA standard header + // Bytes 0..5: LZMA properties + // Bytes 5..13: Uncompressed length + + // Read compressed length + let _ = input.read_u32::()?; + + // Read LZMA propreties to decoder let mut lzma_properties = [0u8; 5]; input.read_exact(&mut lzma_properties)?; + + // Rearrange above into LZMA format let mut lzma_header = Cursor::new(Vec::with_capacity(13)); lzma_header.write_all(&lzma_properties)?; - lzma_header.write_u64::(uncompressed_length as u64)?; - let mut lzma_stream = Stream::new_lzma_decoder(u64::max_value())?; - lzma_stream.process(&lzma_header.into_inner(), &mut [0u8; 1], Action::Run)?; + lzma_header.write_u64::(uncompressed_length.into())?; + + // Create LZMA decoder stream and write header + let mut lzma_stream = Stream::new_lzma_decoder(u64::max_value()).unwrap(); + lzma_stream + .process(&lzma_header.into_inner(), &mut [0u8; 1], Action::Run) + .unwrap(); + + // Decoder is ready Ok(Box::new(XzDecoder::new_stream(input, lzma_stream))) } -#[cfg(not(feature = "lzma-support"))] -fn make_lzma_reader<'a, R: Read + 'a>(_input: R) -> Result> { +#[cfg(not(feature = "lzma"))] +fn make_lzma_reader<'a, R: Read + 'a>( + _input: R, + _uncompressed_length: u32, +) -> Result> { Err(Error::unsupported( "Support for Zlib compressed SWFs is not enabled.", )) @@ -2862,7 +2910,7 @@ pub mod tests { read_from_file("tests/swfs/zlib.swf").header.compression, Compression::Zlib ); - if cfg!(feature = "lzma-support") { + if cfg!(feature = "lzma") { assert_eq!( read_from_file("tests/swfs/lzma.swf").header.compression, Compression::Lzma diff --git a/swf/src/write.rs b/swf/src/write.rs index 64426b261..dfa1b9e87 100644 --- a/swf/src/write.rs +++ b/swf/src/write.rs @@ -104,13 +104,15 @@ fn write_zlib_swf(_output: W, _swf_body: &[u8]) -> Result<()> { )) } -#[cfg(feature = "lzma-support")] +#[cfg(feature = "lzma")] fn write_lzma_swf(mut output: W, swf_body: &[u8]) -> Result<()> { - use xz2::stream::{Action, LzmaOptions, Stream}; - use xz2::write::XzEncoder; - let mut stream = Stream::new_lzma_encoder(&LzmaOptions::new_preset(9)?)?; + use xz2::{ + stream::{Action, LzmaOptions, Stream}, + write::XzEncoder, + }; + let mut stream = Stream::new_lzma_encoder(&LzmaOptions::new_preset(9).unwrap()).unwrap(); let mut lzma_header = [0; 13]; - stream.process(&[], &mut lzma_header, Action::Run)?; + stream.process(&[], &mut lzma_header, Action::Run).unwrap(); // Compressed length. We just write out a dummy value. output.write_u32::(0xffffffff)?; output.write_all(&lzma_header[0..5])?; // LZMA property bytes. @@ -119,7 +121,7 @@ fn write_lzma_swf(mut output: W, swf_body: &[u8]) -> Result<()> { Ok(()) } -#[cfg(not(feature = "lzma-support"))] +#[cfg(not(feature = "lzma"))] fn write_lzma_swf(_output: W, _swf_body: &[u8]) -> Result<()> { Err(Error::unsupported( "Support for LZMA compressed SWFs is not enabled.", @@ -2792,7 +2794,7 @@ mod tests { write_dummy_swf(Compression::Zlib).is_ok(), "Failed to write zlib SWF." ); - if cfg!(feature = "lzma-support") { + if cfg!(feature = "lzma") { assert!( write_dummy_swf(Compression::Lzma).is_ok(), "Failed to write LZMA SWF."