diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 4f2d48836..c6cf273c8 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -769,7 +769,7 @@ fn get_bytes_loaded<'gc>( let bytes_loaded = if movie_clip.is_root() { movie_clip .movie() - .map(|mv| mv.header().uncompressed_length) + .map(|mv| mv.header().uncompressed_len()) .unwrap_or_default() } else { movie_clip.tag_stream_len() as u32 @@ -787,7 +787,7 @@ fn get_bytes_total<'gc>( let bytes_total = if movie_clip.is_root() { movie_clip .movie() - .map(|mv| mv.header().uncompressed_length) + .map(|mv| mv.header().uncompressed_len()) .unwrap_or_default() } else { movie_clip.tag_stream_len() as u32 diff --git a/core/src/avm1/globals/movie_clip_loader.rs b/core/src/avm1/globals/movie_clip_loader.rs index 97d052363..57b2734fc 100644 --- a/core/src/avm1/globals/movie_clip_loader.rs +++ b/core/src/avm1/globals/movie_clip_loader.rs @@ -114,7 +114,7 @@ pub fn get_progress<'gc>( "bytesLoaded", movieclip .movie() - .map(|mv| (mv.header().uncompressed_length).into()) + .map(|mv| (mv.header().uncompressed_len()).into()) .unwrap_or(Value::Undefined), Attribute::empty(), ); @@ -123,7 +123,7 @@ pub fn get_progress<'gc>( "bytesTotal", movieclip .movie() - .map(|mv| (mv.header().uncompressed_length).into()) + .map(|mv| (mv.header().uncompressed_len()).into()) .unwrap_or(Value::Undefined), Attribute::empty(), ); diff --git a/core/src/avm1/timer.rs b/core/src/avm1/timer.rs index 422baece7..ae859744a 100644 --- a/core/src/avm1/timer.rs +++ b/core/src/avm1/timer.rs @@ -37,7 +37,7 @@ impl<'gc> Timers<'gc> { return None; } - let version = context.swf.header().version; + let version = context.swf.version(); let globals = context.avm1.global_object_cell(); let level0 = context.stage.root_clip(); diff --git a/core/src/avm2/globals/flash/display/loaderinfo.rs b/core/src/avm2/globals/flash/display/loaderinfo.rs index c20bb649d..98cae98d3 100644 --- a/core/src/avm2/globals/flash/display/loaderinfo.rs +++ b/core/src/avm2/globals/flash/display/loaderinfo.rs @@ -13,7 +13,7 @@ use crate::avm2::value::Value; use crate::avm2::{AvmString, Error}; use crate::display_object::TDisplayObject; use gc_arena::{GcCell, MutationContext}; -use swf::{write_swf, Compression, Swf}; +use swf::{write_swf, Compression}; /// Implements `flash.display.LoaderInfo`'s instance constructor. pub fn instance_init<'gc>( @@ -173,7 +173,7 @@ pub fn frame_rate<'gc>( return Err("Error: The stage's loader info does not have a frame rate".into()) } LoaderStream::Swf(root, _) => { - return Ok(root.header().frame_rate.into()); + return Ok(root.header().frame_rate().into()); } } } @@ -195,8 +195,8 @@ pub fn height<'gc>( return Err("Error: The stage's loader info does not have a height".into()) } LoaderStream::Swf(root, _) => { - let y_min = root.header().stage_size.y_min; - let y_max = root.header().stage_size.y_max; + let y_min = root.header().stage_size().y_min; + let y_max = root.header().stage_size().y_max; return Ok((y_max - y_min).to_pixels().into()); } } @@ -228,7 +228,7 @@ pub fn swf_version<'gc>( return Err("Error: The stage's loader info does not have a SWF version".into()) } LoaderStream::Swf(root, _) => { - return Ok(root.header().version.into()); + return Ok(root.version().into()); } } } @@ -273,8 +273,8 @@ pub fn width<'gc>( return Err("Error: The stage's loader info does not have a width".into()) } LoaderStream::Swf(root, _) => { - let x_min = root.header().stage_size.x_min; - let x_max = root.header().stage_size.x_max; + let x_min = root.header().stage_size().x_min; + let x_max = root.header().stage_size().x_max; return Ok((x_max - x_min).to_pixels().into()); } } @@ -304,18 +304,10 @@ pub fn bytes<'gc>( // First, write a fake header corresponding to an // uncompressed SWF - let mut header = root.header().clone(); + let mut header = root.header().swf_header().clone(); header.compression = Compression::None; - header.uncompressed_length = root.data().len() as u32; - write_swf( - &Swf { - header, - tags: vec![], - }, - &mut *ba_write, - ) - .unwrap(); + write_swf(&header, &[], &mut *ba_write).unwrap(); // `swf` always writes an implicit end tag, let's cut that // off. We scroll back 2 bytes before writing the actual diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index b68b32909..98a4fac8b 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -964,7 +964,7 @@ impl<'gc> EditText<'gc> { activation.run_with_child_frame_for_display_object( "[Text Field Binding]", parent, - activation.context.swf.header().version, + activation.context.swf.version(), |activation| { if let Ok(Some((object, property))) = activation.resolve_variable_path(parent, &variable) @@ -1053,7 +1053,7 @@ impl<'gc> EditText<'gc> { activation.run_with_child_frame_for_display_object( "[Propagate Text Binding]", self.avm1_parent().unwrap(), - activation.context.swf.header().version, + activation.context.swf.version(), |activation| { let _ = object.set( property, @@ -1204,7 +1204,7 @@ impl<'gc> EditText<'gc> { if changed { let globals = context.avm1.global_object_cell(); - let swf_version = context.swf.header().version; + let swf_version = context.swf.version(); let mut activation = Avm1Activation::from_nothing( context.reborrow(), ActivationIdentifier::root("[Propagate Text Binding]"), diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 10c2cee27..553e2290e 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -185,7 +185,7 @@ impl<'gc> MovieClip<'gc> { /// Construct a movie clip that represents an entire movie. pub fn from_movie(gc_context: MutationContext<'gc, '_>, movie: Arc) -> Self { - let num_frames = movie.header().num_frames; + let num_frames = movie.header().num_frames(); let mc = MovieClip(GcCell::allocate( gc_context, MovieClipData { @@ -525,12 +525,7 @@ impl<'gc> MovieClip<'gc> { ) })?; - Avm1::run_stack_frame_for_init_action( - self.into(), - context.swf.header().version, - slice, - context, - ); + Avm1::run_stack_frame_for_init_action(self.into(), context.swf.version(), slice, context); Ok(()) } @@ -2114,7 +2109,7 @@ impl<'gc> MovieClipData<'gc> { ) { let is_swf = movie.is_some(); let movie = movie.unwrap_or_else(|| Arc::new(SwfMovie::empty(self.movie().version()))); - let total_frames = movie.header().num_frames; + let total_frames = movie.header().num_frames(); self.base.reset_for_movie_load(); self.static_data = Gc::allocate( diff --git a/core/src/library.rs b/core/src/library.rs index 0c81f493f..1bf96570e 100644 --- a/core/src/library.rs +++ b/core/src/library.rs @@ -417,7 +417,7 @@ impl<'gc> Library<'gc> { if !self.movie_libraries.contains_key(&movie) { let slice = SwfSlice::from(movie.clone()); let mut reader = slice.read_from(0); - let movie_version = movie.header().version; + let movie_version = movie.version(); let vm_type = if movie_version > 8 { match reader.read_tag_code_and_length() { Ok((tag_code, _tag_len)) diff --git a/core/src/loader.rs b/core/src/loader.rs index 44434e965..dbc503937 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -127,7 +127,7 @@ impl<'gc> LoadManager<'gc> { fetch: OwnedFuture, Error>, url: String, parameters: Vec<(String, String)>, - on_metadata: Box, + on_metadata: Box, ) -> OwnedFuture<(), Error> { let loader = Loader::RootMovie { self_handle: None }; let handle = self.add_loader(loader); @@ -366,7 +366,7 @@ impl<'gc> Loader<'gc> { fetch: OwnedFuture, Error>, mut url: String, parameters: Vec<(String, String)>, - on_metadata: Box, + on_metadata: Box, ) -> OwnedFuture<(), Error> { let _handle = match self { Loader::RootMovie { self_handle, .. } => { diff --git a/core/src/player.rs b/core/src/player.rs index 7a7caa5ad..4be39aae5 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -346,7 +346,7 @@ impl Player { &mut self, movie_url: &str, parameters: Vec<(String, String)>, - on_metadata: Box, + on_metadata: Box, ) { self.mutate_with_update_context(|context| { let fetch = context.navigator.fetch(movie_url, RequestOptions::get()); @@ -370,12 +370,12 @@ impl Player { pub fn set_root_movie(&mut self, movie: Arc) { info!( "Loaded SWF version {}, with a resolution of {}x{}", - movie.header().version, - movie.header().stage_size.x_max, - movie.header().stage_size.y_max + movie.version(), + movie.header().stage_size().x_max, + movie.header().stage_size().y_max ); - self.frame_rate = movie.header().frame_rate.into(); + self.frame_rate = movie.header().frame_rate().into(); self.swf = movie; self.instance_counter = 0; @@ -626,7 +626,7 @@ impl Player { callback: Object<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { - let version = context.swf.header().version; + let version = context.swf.version(); let globals = context.avm1.global_object_cell(); let root_clip = context.stage.root_clip(); @@ -1167,7 +1167,7 @@ impl Player { Avm1::run_stack_frame_for_action( actions.clip, "[Frame]", - context.swf.header().version, + context.swf.version(), bytecode, context, ); @@ -1194,7 +1194,7 @@ impl Player { let _ = activation.run_child_frame_for_action( "[Actions]", actions.clip, - activation.context.swf.header().version, + activation.context.swf.version(), event, ); } @@ -1212,7 +1212,7 @@ impl Player { Avm1::run_stack_frame_for_action( actions.clip, "[Construct]", - context.swf.header().version, + context.swf.version(), event, context, ); @@ -1223,7 +1223,7 @@ impl Player { Avm1::run_stack_frame_for_method( actions.clip, object, - context.swf.header().version, + context.swf.version(), context, name, &args, diff --git a/core/src/tag_utils.rs b/core/src/tag_utils.rs index 2d9531af8..b34d78ab4 100644 --- a/core/src/tag_utils.rs +++ b/core/src/tag_utils.rs @@ -2,7 +2,7 @@ use crate::backend::navigator::url_from_relative_path; use gc_arena::Collect; use std::path::Path; use std::sync::Arc; -use swf::{Header, TagCode}; +use swf::{HeaderExt, TagCode}; pub type Error = Box; pub type DecodeResult = Result<(), Error>; @@ -14,7 +14,7 @@ pub type SwfStream<'a> = swf::read::Reader<'a>; #[collect(require_static)] pub struct SwfMovie { /// The SWF header parsed from the data stream. - header: Header, + header: HeaderExt, /// Uncompressed SWF data. data: Vec, @@ -40,14 +40,7 @@ impl SwfMovie { /// Construct an empty movie. pub fn empty(swf_version: u8) -> Self { Self { - header: Header { - compression: swf::Compression::None, - version: swf_version, - uncompressed_length: 0, - stage_size: swf::Rectangle::default(), - frame_rate: 1.0, - num_frames: 0, - }, + header: HeaderExt::default_with_swf_version(swf_version), data: vec![], url: None, loader_url: None, @@ -94,7 +87,7 @@ impl SwfMovie { ) -> Result { let compressed_length = swf_data.len(); let swf_buf = swf::read::decompress_swf(swf_data)?; - let encoding = swf::SwfStr::encoding_for_version(swf_buf.header.version); + let encoding = swf::SwfStr::encoding_for_version(swf_buf.header.version()); Ok(Self { header: swf_buf.header, data: swf_buf.data, @@ -106,13 +99,13 @@ impl SwfMovie { }) } - pub fn header(&self) -> &Header { + pub fn header(&self) -> &HeaderExt { &self.header } /// Get the version of the SWF. pub fn version(&self) -> u8 { - self.header.version + self.header.version() } pub fn data(&self) -> &[u8] { @@ -128,11 +121,11 @@ impl SwfMovie { } pub fn width(&self) -> u32 { - (self.header.stage_size.x_max - self.header.stage_size.x_min).to_pixels() as u32 + (self.header.stage_size().x_max - self.header.stage_size().x_min).to_pixels() as u32 } pub fn height(&self) -> u32 { - (self.header.stage_size.y_max - self.header.stage_size.y_min).to_pixels() as u32 + (self.header.stage_size().y_max - self.header.stage_size().y_min).to_pixels() as u32 } /// Get the URL this SWF was fetched from. @@ -296,7 +289,7 @@ impl SwfSlice { /// Get the version of the SWF this data comes from. pub fn version(&self) -> u8 { - self.movie.header().version + self.movie.header().version() } /// Construct a reader for this slice. diff --git a/desktop/src/main.rs b/desktop/src/main.rs index ad32f49e4..e27e48d43 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -526,7 +526,7 @@ fn run_timedemo(opt: Opt) -> Result<(), Box> { .as_ref() .ok_or("Input file necessary for timedemo")?; let (movie, _) = load_movie_from_path(&path, &opt)?; - let movie_frames = Some(movie.header().num_frames); + let movie_frames = Some(movie.header().num_frames()); let viewport_width = 1920; let viewport_height = 1080; diff --git a/swf/examples/reading.rs b/swf/examples/reading.rs index c3318a671..8fa48750a 100644 --- a/swf/examples/reading.rs +++ b/swf/examples/reading.rs @@ -6,6 +6,6 @@ fn main() { let reader = BufReader::new(file); let swf_buf = swf::decompress_swf(reader).unwrap(); let swf = swf::parse_swf(&swf_buf).unwrap(); - println!("The SWF has {} frame(s).", swf.header.num_frames); + println!("The SWF has {} frame(s).", swf.header.num_frames()); println!("The SWF has {} tag(s).", swf.tags.len()); } diff --git a/swf/examples/writing.rs b/swf/examples/writing.rs index bf9acc2fe..8df0952a0 100644 --- a/swf/examples/writing.rs +++ b/swf/examples/writing.rs @@ -1,31 +1,28 @@ use swf::*; fn main() { - let swf = Swf { - header: Header { - compression: Compression::Zlib, - version: 6, - uncompressed_length: 29, - stage_size: Rectangle { - x_min: Twips::from_pixels(0.0), - x_max: Twips::from_pixels(400.0), - y_min: Twips::from_pixels(0.0), - y_max: Twips::from_pixels(400.0), - }, - frame_rate: 60.0, - num_frames: 1, + let header = Header { + compression: Compression::Zlib, + version: 6, + stage_size: Rectangle { + x_min: Twips::from_pixels(0.0), + x_max: Twips::from_pixels(400.0), + y_min: Twips::from_pixels(0.0), + y_max: Twips::from_pixels(400.0), }, - tags: vec![ - Tag::SetBackgroundColor(Color { - r: 255, - g: 0, - b: 0, - a: 255, - }), - Tag::ShowFrame, - ], + frame_rate: 60.0, + num_frames: 1, }; + let tags = [ + Tag::SetBackgroundColor(Color { + r: 255, + g: 0, + b: 0, + a: 255, + }), + Tag::ShowFrame, + ]; let file = std::fs::File::create("tests/swfs/SimpleRedBackground.swf").unwrap(); let writer = std::io::BufWriter::new(file); - swf::write_swf(&swf, writer).unwrap(); + swf::write_swf(&header, &tags, writer).unwrap(); } diff --git a/swf/src/read.rs b/swf/src/read.rs index a17097826..99fa9d79b 100644 --- a/swf/src/read.rs +++ b/swf/src/read.rs @@ -25,10 +25,10 @@ use std::io::{self, Read}; /// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap(); /// let stream = swf::decompress_swf(&data[..]).unwrap(); /// let swf = swf::parse_swf(&stream).unwrap(); -/// println!("Number of frames: {}", swf.header.num_frames); +/// println!("Number of frames: {}", swf.header.num_frames()); /// ``` pub fn parse_swf(swf_buf: &SwfBuf) -> Result> { - let mut reader = Reader::new(&swf_buf.data[..], swf_buf.header.version); + let mut reader = Reader::new(&swf_buf.data[..], swf_buf.header.version()); Ok(Swf { header: swf_buf.header.clone(), @@ -41,18 +41,22 @@ pub fn parse_swf(swf_buf: &SwfBuf) -> Result> { /// /// Returns an `Error` if this is not a valid SWF file. /// +/// This will also parse the first two tags of the SWF file searching +/// for the FileAttributes and SetBackgroundColor tags; this info is +/// returned as an extended header. +/// /// # Example /// ``` /// # std::env::set_current_dir(env!("CARGO_MANIFEST_DIR")); /// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap(); /// let swf_stream = swf::decompress_swf(&data[..]).unwrap(); -/// println!("FPS: {}", swf_stream.header.frame_rate); +/// println!("FPS: {}", swf_stream.header.frame_rate()); /// ``` pub fn decompress_swf<'a, R: Read + 'a>(mut input: R) -> Result { // Read SWF header. let compression = read_compression_type(&mut input)?; let version = input.read_u8()?; - let uncompressed_length = input.read_u32::()?; + let uncompressed_len = input.read_u32::()?; // Now the SWF switches to a compressed stream. let mut decompress_stream: Box = match compression { @@ -75,12 +79,12 @@ pub fn decompress_swf<'a, R: Read + 'a>(mut input: R) -> Result { } // Uncompressed length includes the 4-byte header and 4-byte uncompressed length itself, // subtract it here. - make_lzma_reader(input, uncompressed_length - 8)? + make_lzma_reader(input, uncompressed_len - 8)? } }; // Decompress the entire SWF. - let mut data = Vec::with_capacity(uncompressed_length as usize); + let mut data = Vec::with_capacity(uncompressed_len as usize); if let Err(e) = decompress_stream.read_to_end(&mut data) { log::error!("Error decompressing SWF: {}", e); } @@ -91,7 +95,7 @@ pub fn decompress_swf<'a, R: Read + 'a>(mut input: R) -> Result { // through the stream. // We'll still try to parse what we get if the full decompression fails. // (+ 8 for header size) - if data.len() as u64 + 8 != uncompressed_length as u64 { + if data.len() as u64 + 8 != uncompressed_len as u64 { log::warn!("SWF length doesn't match header, may be corrupt"); } @@ -102,13 +106,45 @@ pub fn decompress_swf<'a, R: Read + 'a>(mut input: R) -> Result { let header = Header { compression, version, - uncompressed_length, stage_size, frame_rate, num_frames, }; let data = reader.get_ref().to_vec(); - Ok(SwfBuf { header, data }) + + // Parse the first two tags, searching for the FileAttributes and SetBackgroundColor tags. + // This metadata is useful, so we want to return it along with the header. + // In SWF8+, FileAttributes should be the first tag in the SWF. + // FileAttributes anywhere else in the SWF are ignored. + let mut tag = reader.read_tag(); + let file_attributes = if let Ok(Tag::FileAttributes(attributes)) = tag { + tag = reader.read_tag(); + attributes + } else { + FileAttributes::default() + }; + + // In most SWFs, SetBackgroundColor will be the second or third tag after FileAttributes + Metadata. + // It's possible for the SetBackgroundColor tag to be missing or appear later in wacky SWFs, so let's + // return `None` in this case. + let mut background_color = None; + for _ in 0..2 { + if let Ok(Tag::SetBackgroundColor(color)) = tag { + background_color = Some(color); + break; + }; + tag = reader.read_tag(); + } + + Ok(SwfBuf { + header: HeaderExt { + header, + file_attributes, + background_color, + uncompressed_len, + }, + data, + }) } #[cfg(feature = "flate2")] @@ -304,7 +340,7 @@ impl<'a> Reader<'a> { /// # std::env::set_current_dir(env!("CARGO_MANIFEST_DIR")); /// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap(); /// let mut swf_buf = swf::decompress_swf(&data[..]).unwrap(); - /// let mut reader = swf::read::Reader::new(&swf_buf.data[..], swf_buf.header.version); + /// let mut reader = swf::read::Reader::new(&swf_buf.data[..], swf_buf.header.version()); /// while let Ok(tag) = reader.read_tag() { /// println!("Tag: {:?}", tag); /// } @@ -2698,7 +2734,7 @@ pub mod tests { let mut tag_header_length; loop { let (swf_tag_code, length) = { - let mut tag_reader = Reader::new(&data[pos..], swf_buf.header.version); + let mut tag_reader = Reader::new(&data[pos..], swf_buf.header.version()); let ret = tag_reader.read_tag_code_and_length().unwrap(); tag_header_length = tag_reader.get_ref().as_ptr() as usize - (pos + data.as_ptr() as usize); @@ -2746,16 +2782,16 @@ pub mod tests { assert_eq!( read_from_file("tests/swfs/uncompressed.swf") .header - .compression, + .compression(), Compression::None ); assert_eq!( - read_from_file("tests/swfs/zlib.swf").header.compression, + read_from_file("tests/swfs/zlib.swf").header.compression(), Compression::Zlib ); if cfg!(feature = "lzma") { assert_eq!( - read_from_file("tests/swfs/lzma.swf").header.compression, + read_from_file("tests/swfs/lzma.swf").header.compression(), Compression::Lzma ); } diff --git a/swf/src/test_data.rs b/swf/src/test_data.rs index 00fb022a7..b5ab9863a 100644 --- a/swf/src/test_data.rs +++ b/swf/src/test_data.rs @@ -18,7 +18,7 @@ pub fn echo_swf(filename: &str) { let swf_buf = decompress_swf(&in_data[..]).unwrap(); let swf = parse_swf(&swf_buf).unwrap(); let out_file = File::create(filename).unwrap(); - write_swf(&swf, out_file).unwrap(); + write_swf(&swf.header.swf_header(), &swf.tags, out_file).unwrap(); } pub type TestData = (u8, T, Vec); diff --git a/swf/src/types.rs b/swf/src/types.rs index fcc22f4b2..c54021945 100644 --- a/swf/src/types.rs +++ b/swf/src/types.rs @@ -14,9 +14,9 @@ pub use matrix::Matrix; /// A complete header and tags in the SWF file. /// This is returned by the `swf::read_swf` convenience method. -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub struct Swf<'a> { - pub header: Header, + pub header: HeaderExt, pub tags: Vec>, } @@ -24,7 +24,7 @@ pub struct Swf<'a> { /// Owns the decompressed SWF data, which will be referenced when parsed by `parse_swf`. pub struct SwfBuf { /// The parsed SWF header. - pub header: Header, + pub header: HeaderExt, /// The decompressed SWF tag stream. pub data: Vec, @@ -39,17 +39,143 @@ pub struct SwfBuf { pub struct Header { pub compression: Compression, pub version: u8, - pub uncompressed_length: u32, pub stage_size: Rectangle, pub frame_rate: f32, pub num_frames: u16, } +impl Header { + pub fn default_with_swf_version(version: u8) -> Self { + Self { + compression: Compression::None, + version, + stage_size: Default::default(), + frame_rate: 1.0, + num_frames: 0, + } + } +} + +/// The extended metadata of an SWF file. +/// +/// This includes the SWF header data as well as metdata from the FileAttributes and +/// SetBackgroundColor tags. +/// +/// This metadata may not reflect the actual data inside a malformed SWF; for example, +/// the root timeline my actually contain fewer frames than `HeaderExt::num_frames` if it is +/// corrupted. +#[derive(Clone, Debug)] +pub struct HeaderExt { + pub(crate) header: Header, + pub(crate) file_attributes: FileAttributes, + pub(crate) background_color: Option, + pub(crate) uncompressed_len: u32, +} + +impl HeaderExt { + #[inline] + /// Returns the header for a dummy SWF file with the given SWF version. + pub fn default_with_swf_version(version: u8) -> Self { + Self { + header: Header::default_with_swf_version(version), + file_attributes: Default::default(), + background_color: None, + uncompressed_len: 0, + } + } + + /// The background color of the SWF from the SetBackgroundColor tag. + /// + /// `None` will be returned if the SetBackgroundColor tag was not found. + #[inline] + pub fn background_color(&self) -> Option { + self.background_color.clone() + } + + /// The compression format used by the SWF. + #[inline] + pub fn compression(&self) -> Compression { + self.header.compression + } + + /// The frame rate of the SWF, in frames per second. + #[inline] + pub fn frame_rate(&self) -> f32 { + self.header.frame_rate + } + + /// Whether this SWF contains XMP metadata in a Metadata tag. + #[inline] + pub fn has_metdata(&self) -> bool { + self.file_attributes.contains(FileAttributes::HAS_METADATA) + } + + /// Returns the basic SWF header. + #[inline] + pub fn swf_header(&self) -> &Header { + &self.header + } + + /// Whether this SWF uses ActionScript 3.0 (AVM2). + #[inline] + pub fn is_action_script_3(&self) -> bool { + self.file_attributes + .contains(FileAttributes::IS_ACTION_SCRIPT_3) + } + + /// The number of frames on the root timeline. + #[inline] + pub fn num_frames(&self) -> u16 { + self.header.num_frames + } + + /// The stage dimensions of this SWF. + #[inline] + pub fn stage_size(&self) -> &Rectangle { + &self.header.stage_size + } + + /// The SWF version. + #[inline] + pub fn version(&self) -> u8 { + self.header.version + } + + /// The length of the SWF after decompression. + #[inline] + pub fn uncompressed_len(&self) -> u32 { + self.uncompressed_len + } + + /// Whether this SWF requests hardware acceleration to blit to the screen. + #[inline] + pub fn use_direct_blit(&self) -> bool { + self.file_attributes + .contains(FileAttributes::USE_DIRECT_BLIT) + } + + /// Whether this SWF requests hardware acceleration for compositing. + #[inline] + pub fn use_gpu(&self) -> bool { + self.file_attributes.contains(FileAttributes::USE_GPU) + } + + /// Whether this SWF should be placed in the network sandbox when run locally. + /// + /// SWFs in the network sandbox can only access network resources, not local resources. + /// SWFs in the local sandbox can only access local resources, not network resources. + #[inline] + pub fn use_network_sandbox(&self) -> bool { + self.file_attributes + .contains(FileAttributes::USE_NETWORK_SANDBOX) + } +} + /// The compression format used internally by the SWF file. /// /// The vast majority of SWFs will use zlib compression. /// [SWF19 p.27](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=27) -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Compression { None, Zlib, @@ -393,6 +519,13 @@ bitflags! { } } +impl Default for FileAttributes { + fn default() -> Self { + // The settings for SWF7 and earlier, which contain no FileAttributes tag. + Self::empty() + } +} + #[derive(Debug, PartialEq)] pub struct FrameLabel<'a> { pub label: &'a SwfStr, diff --git a/swf/src/write.rs b/swf/src/write.rs index c65bacb28..cc43df1ad 100644 --- a/swf/src/write.rs +++ b/swf/src/write.rs @@ -21,43 +21,40 @@ use std::io::{self, Write}; /// ``` /// use swf::*; /// -/// let swf = Swf { -/// header: Header { +/// let header = Header { /// compression: Compression::Zlib, /// version: 6, -/// uncompressed_length: 1024, /// stage_size: Rectangle { x_min: Twips::from_pixels(0.0), x_max: Twips::from_pixels(400.0), y_min: Twips::from_pixels(0.0), y_max: Twips::from_pixels(400.0) }, /// frame_rate: 60.0, /// num_frames: 1, -/// }, -/// tags: vec![ +/// }; +/// let tags = [ /// Tag::SetBackgroundColor(Color { r: 255, g: 0, b: 0, a: 255 }), /// Tag::ShowFrame -/// ] -/// }; +/// ]; /// let output = Vec::new(); -/// swf::write_swf(&swf, output).unwrap(); +/// swf::write_swf(&header, &tags, output).unwrap(); /// ``` -pub fn write_swf(swf: &Swf, mut output: W) -> Result<()> { - let signature = match swf.header.compression { +pub fn write_swf(header: &Header, tags: &[Tag<'_>], mut output: W) -> Result<()> { + let signature = match header.compression { Compression::None => b"FWS", Compression::Zlib => b"CWS", Compression::Lzma => b"ZWS", }; output.write_all(&signature[..])?; - output.write_u8(swf.header.version)?; + output.write_u8(header.version)?; // Write SWF body. let mut swf_body = Vec::new(); { - let mut writer = Writer::new(&mut swf_body, swf.header.version); + let mut writer = Writer::new(&mut swf_body, header.version); - writer.write_rectangle(&swf.header.stage_size)?; - writer.write_fixed8(swf.header.frame_rate)?; - writer.write_u16(swf.header.num_frames)?; + writer.write_rectangle(&header.stage_size)?; + writer.write_fixed8(header.frame_rate)?; + writer.write_u16(header.num_frames)?; // Write main timeline tag list. - writer.write_tag_list(&swf.tags)?; + writer.write_tag_list(&tags)?; } // Write SWF header. @@ -65,7 +62,7 @@ pub fn write_swf(swf: &Swf, mut output: W) -> Result<()> { output.write_u32::(swf_body.len() as u32 + 8)?; // Compress SWF body. - match swf.header.compression { + match header.compression { Compression::None => { output.write_all(&swf_body)?; } @@ -2650,12 +2647,13 @@ mod tests { use super::*; use crate::test_data; - fn new_swf() -> Swf<'static> { - Swf { - header: Header { - compression: Compression::Zlib, + #[test] + fn write_swfs() { + fn write_dummy_swf(compression: Compression) -> Result<()> { + let mut buf = Vec::new(); + let header = Header { + compression, version: 13, - uncompressed_length: 1024, stage_size: Rectangle { x_min: Twips::from_pixels(0.0), x_max: Twips::from_pixels(640.0), @@ -2664,18 +2662,8 @@ mod tests { }, frame_rate: 60.0, num_frames: 1, - }, - tags: vec![], - } - } - - #[test] - fn write_swfs() { - fn write_dummy_swf(compression: Compression) -> Result<()> { - let mut buf = Vec::new(); - let mut swf = new_swf(); - swf.header.compression = compression; - write_swf(&swf, &mut buf)?; + }; + write_swf(&header, &[], &mut buf)?; Ok(()) } assert!( diff --git a/tests/tests/regression_tests.rs b/tests/tests/regression_tests.rs index 4deafdb0a..166ad2ecb 100644 --- a/tests/tests/regression_tests.rs +++ b/tests/tests/regression_tests.rs @@ -880,7 +880,7 @@ fn run_swf( let base_path = Path::new(swf_path).parent().unwrap(); let (mut executor, channel) = NullExecutor::new(); let movie = SwfMovie::from_path(swf_path, None)?; - let frame_time = 1000.0 / movie.header().frame_rate as f64; + let frame_time = 1000.0 / movie.header().frame_rate() as f64; let trace_output = Rc::new(RefCell::new(Vec::new())); let player = Player::new( diff --git a/web/src/lib.rs b/web/src/lib.rs index b7fa266c1..5f1496aea 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -207,7 +207,7 @@ impl Ruffle { let parameters_to_load = parse_movie_parameters(¶meters); let ruffle = *self; - let on_metadata = move |swf_header: &ruffle_core::swf::Header| { + let on_metadata = move |swf_header: &ruffle_core::swf::HeaderExt| { ruffle.on_metadata(swf_header); }; @@ -976,16 +976,16 @@ impl Ruffle { }); } - fn on_metadata(&self, swf_header: &ruffle_core::swf::Header) { + fn on_metadata(&self, swf_header: &ruffle_core::swf::HeaderExt) { let _ = self.with_instance(|instance| { - let width = swf_header.stage_size.x_max - swf_header.stage_size.x_min; - let height = swf_header.stage_size.y_max - swf_header.stage_size.y_min; + let width = swf_header.stage_size().x_max - swf_header.stage_size().x_min; + let height = swf_header.stage_size().y_max - swf_header.stage_size().y_min; let metadata = MovieMetadata { width: width.to_pixels(), height: height.to_pixels(), - frame_rate: swf_header.frame_rate, - num_frames: swf_header.num_frames, - swf_version: swf_header.version, + frame_rate: swf_header.frame_rate(), + num_frames: swf_header.num_frames(), + swf_version: swf_header.version(), }; if let Ok(value) = JsValue::from_serde(&metadata) {