swf: Use lzma-rs for LZMA SWFs (fix #405)

Pure Rust decoder that functions on desktop and wasm.
Enable lzma feature by default.

TODO: Switch to lzma-rs streaming API when stable on crates.io.
Currently decodes entire stream at once.
This commit is contained in:
Mike Welsh 2020-12-17 12:58:04 -08:00
parent beed570475
commit 7cf217a911
6 changed files with 60 additions and 72 deletions

37
Cargo.lock generated
View File

@ -290,6 +290,12 @@ dependencies = [
"serde",
]
[[package]]
name = "build_const"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
[[package]]
name = "bumpalo"
version = "3.4.0"
@ -794,6 +800,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "crc"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
dependencies = [
"build_const",
]
[[package]]
name = "crc32fast"
version = "1.2.1"
@ -2232,14 +2247,13 @@ dependencies = [
]
[[package]]
name = "lzma-sys"
version = "0.1.17"
name = "lzma-rs"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb4b7c3eddad11d3af9e86c487607d2d2442d185d848575365c4856ba96d619"
checksum = "adc181f57e3b64bb860c476fe5751eb6f60e9fcf588b1447e24c0757c1c3101f"
dependencies = [
"cc",
"libc",
"pkg-config",
"byteorder",
"crc",
]
[[package]]
@ -3844,10 +3858,10 @@ dependencies = [
"flate2",
"libflate 1.0.3",
"log",
"lzma-rs",
"num-derive",
"num-traits",
"smallvec 1.5.1",
"xz2",
]
[[package]]
@ -4789,12 +4803,3 @@ name = "xml-rs"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
[[package]]
name = "xz2"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c179869f34fc7c01830d3ce7ea2086bc3a07e0d35289b667d0a8bf910258926c"
dependencies = [
"lzma-sys",
]

View File

@ -80,21 +80,10 @@ impl SwfMovie {
// 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.
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) {
return Err(format!("Error decompressing SWF, may be corrupt: {}", e).into());
}
data
};
Ok(Self {
header,

View File

@ -18,11 +18,11 @@ libflate = {version = "1.0", optional = true}
log = "0.4"
smallvec = "1.5.1"
flate2 = {version = "1.0", optional = true}
xz2 = {version = "0.1.6", optional = true}
lzma-rs = {version = "0.1.3", optional = true }
[dev-dependencies]
approx = "0.4.0"
[features]
default = ["flate2"]
lzma = ["xz2"]
default = ["flate2", "lzma"]
lzma = ["lzma-rs"]

View File

@ -16,8 +16,6 @@ extern crate libflate;
#[macro_use]
extern crate num_derive;
extern crate num_traits;
#[cfg(feature = "lzma")]
extern crate xz2;
pub mod avm1;
pub mod avm2;

View File

@ -156,11 +156,9 @@ fn make_lzma_reader<'a, R: Read + 'a>(
mut input: R,
uncompressed_length: u32,
) -> Result<Box<dyn Read + 'a>> {
use byteorder::WriteBytesExt;
use std::io::{Cursor, Write};
use xz2::{
read::XzDecoder,
stream::{Action, Stream},
use lzma_rs::{
decompress::{Options, UnpackedSize},
lzma_decompress_with_options,
};
// 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
@ -174,27 +172,24 @@ fn make_lzma_reader<'a, R: Read + 'a>(
// LZMA standard header
// Bytes 0..5: LZMA properties
// Bytes 5..13: Uncompressed length
//
// To deal with the mangled header, use lzma_rs options to anually provide uncompressed length.
// Read compressed length
// Read compressed length (ignored)
let _ = input.read_u32::<LittleEndian>()?;
// Read LZMA propreties to decoder
let mut lzma_properties = [0u8; 5];
input.read_exact(&mut lzma_properties)?;
// TODO: Switch to lzma-rs streaming API when stable.
let mut output = Vec::with_capacity(uncompressed_length as usize);
lzma_decompress_with_options(
&mut io::BufReader::new(input),
&mut output,
&Options {
unpacked_size: UnpackedSize::UseProvided(Some(uncompressed_length.into())),
},
)
.map_err(|_| Error::invalid_data("Unable to decompress LZMA SWF."))?;
// 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::<LittleEndian>(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)))
Ok(Box::new(io::Cursor::new(output)))
}
#[cfg(not(feature = "lzma"))]

View File

@ -72,7 +72,11 @@ pub fn write_swf<W: Write>(swf: &Swf, mut output: W) -> Result<()> {
// SWF format has a mangled LZMA header, so we have to do some magic to convert the
// standard LZMA header to SWF format.
// https://adobe.ly/2s8oYzn
Compression::Lzma => write_lzma_swf(&mut output, &swf_body)?,
Compression::Lzma => {
write_lzma_swf(&mut output, &swf_body)?;
// 5 bytes of garbage data?
//output.write_all(&[0xFF, 0xB5, 0xE6, 0xF8, 0xCB])?;
}
};
Ok(())
@ -105,19 +109,16 @@ fn write_zlib_swf<W: Write>(_output: W, _swf_body: &[u8]) -> Result<()> {
}
#[cfg(feature = "lzma")]
fn write_lzma_swf<W: Write>(mut output: W, swf_body: &[u8]) -> Result<()> {
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).unwrap();
// Compressed length. We just write out a dummy value.
output.write_u32::<LittleEndian>(0xffffffff)?;
output.write_all(&lzma_header[0..5])?; // LZMA property bytes.
let mut encoder = XzEncoder::new_stream(&mut output, stream);
encoder.write_all(&swf_body)?;
fn write_lzma_swf<W: Write>(mut output: W, mut swf_body: &[u8]) -> Result<()> {
// Compress data using LZMA.
let mut data = vec![];
lzma_rs::lzma_compress(&mut swf_body, &mut data)?;
// Flash uses a mangled LZMA header, so we have to massage it into the SWF format.
// https://helpx.adobe.com/flash-player/kb/exception-thrown-you-decompress-lzma-compressed.html
output.write_u32::<LittleEndian>(data.len() as u32 - 13)?; // Compressed length (- 13 to not include lzma header)
output.write_all(&data[0..5])?; // LZMA properties
output.write_all(&data[13..])?; // Data
Ok(())
}