From e1d18be7fa9e6594d27a42e6ac8eff72b1fb22bb Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 12 Feb 2022 21:00:53 -0500 Subject: [PATCH] core: First pass at image loading through `loadMovie` et all --- core/src/loader.rs | 170 +++++++++++++++++++++++++++++++++------------ 1 file changed, 126 insertions(+), 44 deletions(-) diff --git a/core/src/loader.rs b/core/src/loader.rs index 45c593123..e833d9f72 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -4,8 +4,11 @@ use crate::avm1::activation::{Activation, ActivationIdentifier}; use crate::avm1::{Avm1, Object, TObject, Value}; use crate::avm2::{Activation as Avm2Activation, Domain as Avm2Domain}; use crate::backend::navigator::{OwnedFuture, RequestOptions}; +use crate::backend::render::{determine_jpeg_tag_format, JpegTagFormat}; use crate::context::{ActionQueue, ActionType}; -use crate::display_object::{DisplayObject, MorphShape, TDisplayObject}; +use crate::display_object::{ + Bitmap, DisplayObject, MorphShape, TDisplayObject, TDisplayObjectContainer, +}; use crate::player::{Player, NEWEST_PLAYER_VERSION}; use crate::string::AvmString; use crate::tag_utils::SwfMovie; @@ -14,11 +17,45 @@ use encoding_rs::UTF_8; use gc_arena::{Collect, CollectionContext}; use generational_arena::{Arena, Index}; use std::sync::{Arc, Mutex, Weak}; +use swf::read::read_compression_type; use thiserror::Error; use url::form_urlencoded; pub type Handle = Index; +/// Enumeration of all content types that `Loader` can handle. +/// +/// This is a superset of `JpegTagFormat`. +#[derive(PartialEq)] +pub enum ContentType { + Swf, + Jpeg, + Png, + Gif, + Unknown, +} + +impl From for ContentType { + fn from(jtf: JpegTagFormat) -> Self { + match jtf { + JpegTagFormat::Jpeg => Self::Jpeg, + JpegTagFormat::Png => Self::Png, + JpegTagFormat::Gif => Self::Gif, + JpegTagFormat::Unknown => Self::Unknown, + } + } +} + +impl ContentType { + fn sniff(data: &[u8]) -> ContentType { + if read_compression_type(data).is_ok() { + ContentType::Swf + } else { + determine_jpeg_tag_format(data).into() + } + } +} + #[derive(Error, Debug)] pub enum Error { #[error("Load cancelled")] @@ -42,6 +79,9 @@ pub enum Error { #[error("Invalid SWF")] InvalidSwf(#[from] crate::tag_utils::Error), + #[error("Invalid data")] + InvalidData, + // TODO: We can't support lifetimes on this error object yet (or we'll need some backends inside // the GC arena). We're losing info here. How do we fix that? #[error("Error running avm1 script: {0}")] @@ -389,12 +429,15 @@ impl<'gc> Loader<'gc> { })?; if let Ok(data) = fetch.await { - let movie = Arc::new(SwfMovie::from_data( - &data, - Some(url.into_owned()), - loader_url, - )?); - if replacing_root_movie { + let sniffed_type = ContentType::sniff(&data); + let length = data.len(); + + //TODO: Does replacing the root movie with an image require any + //special work? + if replacing_root_movie && sniffed_type == ContentType::Swf { + let movie = + SwfMovie::from_data(&data, Some(url.into_owned()), loader_url.clone())?; + let movie = Arc::new(movie); player.lock().unwrap().set_root_movie(movie); return Ok(()); } @@ -410,46 +453,83 @@ impl<'gc> Loader<'gc> { _ => unreachable!(), }; - let mut activation = Avm2Activation::from_nothing(uc.reborrow()); - let parent_domain = activation.avm2().global_domain(); - let domain = Avm2Domain::movie_domain(&mut activation, parent_domain); - uc.library - .library_for_movie_mut(movie.clone()) - .set_avm2_domain(domain); + match sniffed_type { + ContentType::Swf => { + let movie = Arc::new(SwfMovie::from_data( + &data, + Some(url.into_owned()), + loader_url, + )?); - if let Some(broadcaster) = broadcaster { - Avm1::run_stack_frame_for_method( - clip, - broadcaster, - NEWEST_PLAYER_VERSION, - uc, - "broadcastMessage".into(), - &[ - "onLoadProgress".into(), - clip.object(), - data.len().into(), - data.len().into(), - ], - ); - } - - if let Some(mut mc) = clip.as_movie_clip() { - mc.replace_with_movie(uc.gc_context, Some(movie.clone())); - mc.post_instantiation(uc, None, Instantiator::Movie, false); - - let mut morph_shapes = fnv::FnvHashMap::default(); - mc.preload(uc, &mut morph_shapes); - - // Finalize morph shapes. - for (id, static_data) in morph_shapes { - let morph_shape = MorphShape::new(uc.gc_context, static_data); + let mut activation = Avm2Activation::from_nothing(uc.reborrow()); + let parent_domain = activation.avm2().global_domain(); + let domain = Avm2Domain::movie_domain(&mut activation, parent_domain); uc.library .library_for_movie_mut(movie.clone()) - .register_character( - id, - crate::character::Character::MorphShape(morph_shape), + .set_avm2_domain(domain); + + if let Some(broadcaster) = broadcaster { + Avm1::run_stack_frame_for_method( + clip, + broadcaster, + NEWEST_PLAYER_VERSION, + uc, + "broadcastMessage".into(), + &[ + "onLoadProgress".into(), + clip.object(), + data.len().into(), + data.len().into(), + ], ); + } + + if let Some(mut mc) = clip.as_movie_clip() { + mc.replace_with_movie(uc.gc_context, Some(movie.clone())); + mc.post_instantiation(uc, None, Instantiator::Movie, false); + + let mut morph_shapes = fnv::FnvHashMap::default(); + mc.preload(uc, &mut morph_shapes); + + // Finalize morph shapes. + for (id, static_data) in morph_shapes { + let morph_shape = MorphShape::new(uc.gc_context, static_data); + uc.library + .library_for_movie_mut(movie.clone()) + .register_character( + id, + crate::character::Character::MorphShape(morph_shape), + ); + } + } } + ContentType::Gif | ContentType::Jpeg | ContentType::Png => { + let bitmap = uc.renderer.register_bitmap_jpeg_2(&data)?; + let bitmap_obj = + Bitmap::new(uc, 0, bitmap.handle, bitmap.width, bitmap.height); + + if let Some(broadcaster) = broadcaster { + Avm1::run_stack_frame_for_method( + clip, + broadcaster, + NEWEST_PLAYER_VERSION, + uc, + "broadcastMessage".into(), + &[ + "onLoadProgress".into(), + clip.object(), + length.into(), + length.into(), + ], + ); + } + + if let Some(mc) = clip.as_movie_clip() { + //TODO: Somehow replace with Bitmap? + mc.replace_at_depth(uc, bitmap_obj.into(), 1); + } + } + ContentType::Unknown => return Err(Error::InvalidData), } if let Some(broadcaster) = broadcaster { @@ -471,7 +551,7 @@ impl<'gc> Loader<'gc> { }; Ok(()) - }) + })?; //TODO: content sniffing errors need to be reported somehow } else { //TODO: Inspect the fetch error. //This requires cooperation from the backend to send abstract @@ -511,8 +591,10 @@ impl<'gc> Loader<'gc> { }; Ok(()) - }) + })?; } + + Ok(()) }) }