diff --git a/core/src/avm1.rs b/core/src/avm1.rs index 5fdff2d24..ffb441fd5 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -979,7 +979,7 @@ impl<'gc> Avm1<'gc> { /// If the level does not exist, then it will be created and instantiated /// with a script object. pub fn resolve_level( - &self, + &mut self, level_id: u32, context: &mut UpdateContext<'_, 'gc, '_>, ) -> DisplayObject<'gc> { @@ -989,7 +989,7 @@ impl<'gc> Avm1<'gc> { let mut level: DisplayObject<'_> = MovieClip::new(NEWEST_PLAYER_VERSION, context.gc_context).into(); - level.post_instantiation(context.gc_context, level, self.prototypes.movie_clip); + level.post_instantiation(self, context, level); level.set_depth(context.gc_context, level_id as i32); context.levels.insert(level_id, level); @@ -1804,7 +1804,7 @@ impl<'gc> Avm1<'gc> { if let Some(clip) = self.target_clip() { if let Some(clip) = clip.as_movie_clip() { // The frame on the stack is 0-based, not 1-based. - clip.goto_frame(context, frame + 1, true); + clip.goto_frame(self, context, frame + 1, true); } else { log::error!("GotoFrame failed: Target is not a MovieClip"); } @@ -1850,7 +1850,7 @@ impl<'gc> Avm1<'gc> { if let Some(clip) = self.target_clip() { if let Some(clip) = clip.as_movie_clip() { if let Some(frame) = clip.frame_label_to_number(label) { - clip.goto_frame(context, frame, true); + clip.goto_frame(self, context, frame, true); } else { log::warn!("GoToLabel: Frame label '{}' not found", label); } @@ -2052,7 +2052,7 @@ impl<'gc> Avm1<'gc> { fn action_next_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { if let Some(clip) = self.target_clip() { if let Some(clip) = clip.as_movie_clip() { - clip.next_frame(context); + clip.next_frame(self, context); } else { log::warn!("NextFrame: Target is not a MovieClip"); } @@ -2165,7 +2165,7 @@ impl<'gc> Avm1<'gc> { fn action_prev_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { if let Some(clip) = self.target_clip() { if let Some(clip) = clip.as_movie_clip() { - clip.prev_frame(context); + clip.prev_frame(self, context); } else { log::warn!("PrevFrame: Target is not a MovieClip"); } diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index 9f489fd36..ed64b50a3 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -179,12 +179,7 @@ pub fn create_globals<'gc>( boolean::create_proto(gc_context, object_proto, function_proto); //TODO: These need to be constructors and should also set `.prototype` on each one - let object = FunctionObject::function( - gc_context, - Executable::Native(object::constructor), - Some(function_proto), - Some(object_proto), - ); + let object = object::create_object_object(gc_context, object_proto, function_proto); let color = FunctionObject::function( gc_context, diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 4860073dd..6a3239b3a 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -229,14 +229,13 @@ fn attach_movie<'gc>( .library .library_for_movie(movie_clip.movie().unwrap()) .ok_or_else(|| "Movie is missing!".into()) - .and_then(|l| { - l.instantiate_by_export_name(&export_name, context.gc_context, &avm.prototypes) - }) + .and_then(|l| l.instantiate_by_export_name(&export_name, context.gc_context)) { + new_clip.post_instantiation(avm, context, new_clip); // Set name and attach to parent. new_clip.set_name(context.gc_context, &new_instance_name); movie_clip.add_child_from_avm(context, new_clip, depth); - new_clip.run_frame(context); + new_clip.run_frame(avm, context); // Copy properties from init_object to the movieclip. let new_clip = new_clip.object().as_object().unwrap(); @@ -272,16 +271,12 @@ fn create_empty_movie_clip<'gc>( // Create empty movie clip. let mut new_clip = MovieClip::new(avm.current_swf_version(), context.gc_context); - new_clip.post_instantiation( - context.gc_context, - new_clip.into(), - avm.prototypes.movie_clip, - ); + new_clip.post_instantiation(avm, context, new_clip.into()); // Set name and attach to parent. new_clip.set_name(context.gc_context, &new_instance_name); movie_clip.add_child_from_avm(context, new_clip.into(), depth); - new_clip.run_frame(context); + new_clip.run_frame(avm, context); Ok(new_clip.object().into()) } @@ -326,7 +321,7 @@ fn create_text_field<'gc>( let mut text_field: DisplayObject<'gc> = EditText::new(context, movie, x, y, width, height).into(); - text_field.post_instantiation(context.gc_context, text_field, avm.prototypes().text_field); + text_field.post_instantiation(avm, context, text_field); text_field.set_name(context.gc_context, &instance_name); movie_clip.add_child_from_avm(context, text_field, depth as Depth); @@ -384,8 +379,10 @@ pub fn duplicate_movie_clip_with_bias<'gc>( .library .library_for_movie(movie_clip.movie().unwrap()) .ok_or_else(|| "Movie is missing!".into()) - .and_then(|l| l.instantiate_by_id(movie_clip.id(), context.gc_context, &avm.prototypes)) + .and_then(|l| l.instantiate_by_id(movie_clip.id(), context.gc_context)) { + new_clip.post_instantiation(avm, context, new_clip); + // Set name and attach to parent. new_clip.set_name(context.gc_context, &new_instance_name); parent.add_child_from_avm(context, new_clip, depth); @@ -395,7 +392,7 @@ pub fn duplicate_movie_clip_with_bias<'gc>( new_clip.set_color_transform(context.gc_context, &*movie_clip.color_transform()); // TODO: Any other properties we should copy...? // Definitely not ScriptObject properties. - new_clip.run_frame(context); + new_clip.run_frame(avm, context); // Copy properties from init_object to the movieclip. let new_clip = new_clip.object().as_object().unwrap(); @@ -506,7 +503,7 @@ pub fn goto_frame<'gc>( frame = frame.wrapping_sub(1); frame = frame.wrapping_add(i32::from(scene_offset)); if frame >= 0 { - movie_clip.goto_frame(context, frame.saturating_add(1) as u16, stop); + movie_clip.goto_frame(avm, context, frame.saturating_add(1) as u16, stop); } } val => { @@ -514,7 +511,7 @@ pub fn goto_frame<'gc>( let frame_label = val.clone().coerce_to_string(avm, context)?; if let Some(mut frame) = movie_clip.frame_label_to_number(&frame_label) { frame = frame.wrapping_add(scene_offset); - movie_clip.goto_frame(context, frame, stop); + movie_clip.goto_frame(avm, context, frame, stop); } } } @@ -523,11 +520,11 @@ pub fn goto_frame<'gc>( fn next_frame<'gc>( movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], ) -> Result, Error> { - movie_clip.next_frame(context); + movie_clip.next_frame(avm, context); Ok(Value::Undefined.into()) } @@ -543,11 +540,11 @@ fn play<'gc>( fn prev_frame<'gc>( movie_clip: MovieClip<'gc>, - _avm: &mut Avm1<'gc>, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _args: &[Value<'gc>], ) -> Result, Error> { - movie_clip.prev_frame(context); + movie_clip.prev_frame(avm, context); Ok(Value::Undefined.into()) } diff --git a/core/src/avm1/globals/object.rs b/core/src/avm1/globals/object.rs index 1aa953cca..04fba32cb 100644 --- a/core/src/avm1/globals/object.rs +++ b/core/src/avm1/globals/object.rs @@ -1,7 +1,9 @@ //! Object prototype +use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::property::Attribute::{self, *}; use crate::avm1::return_value::ReturnValue; use crate::avm1::{Avm1, Error, Object, TObject, UpdateContext, Value}; +use crate::character::Character; use enumset::EnumSet; use gc_arena::MutationContext; @@ -129,6 +131,30 @@ fn value_of<'gc>( Ok(ReturnValue::Immediate(this.into())) } +/// Implements `Object.registerClass` +pub fn register_class<'gc>( + avm: &mut Avm1<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + _this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(class_name) = args.get(0).cloned() { + let class_name = class_name.coerce_to_string(avm, context)?; + if let Some(Character::MovieClip(movie_clip)) = context + .library + .library_for_movie_mut(context.swf.clone()) + .get_character_by_export_name(&class_name) + { + if let Some(constructor) = args.get(1) { + movie_clip.set_avm1_constructor(context.gc_context, Some(constructor.as_object()?)); + } else { + movie_clip.set_avm1_constructor(context.gc_context, None); + } + } + } + Ok(Value::Undefined.into()) +} + /// Partially construct `Object.prototype`. /// /// `__proto__` and other cross-linked properties of this object will *not* @@ -259,3 +285,27 @@ pub fn as_set_prop_flags<'gc>( Ok(Value::Undefined.into()) } + +pub fn create_object_object<'gc>( + gc_context: MutationContext<'gc, '_>, + proto: Object<'gc>, + fn_proto: Object<'gc>, +) -> Object<'gc> { + let object_function = FunctionObject::function( + gc_context, + Executable::Native(constructor), + Some(fn_proto), + Some(proto), + ); + let mut object = object_function.as_script_object().unwrap(); + + object.force_set_function( + "registerClass", + register_class, + gc_context, + Attribute::DontEnum | Attribute::DontDelete | Attribute::ReadOnly, + Some(fn_proto), + ); + + object_function +} diff --git a/core/src/avm1/script_object.rs b/core/src/avm1/script_object.rs index bbdec05b9..2af09af49 100644 --- a/core/src/avm1/script_object.rs +++ b/core/src/avm1/script_object.rs @@ -587,7 +587,6 @@ mod tests { let mut avm = Avm1::new(gc_context, swf_version); let swf = Arc::new(SwfMovie::empty(swf_version)); let mut root: DisplayObject<'_> = MovieClip::new(swf_version, gc_context).into(); - root.post_instantiation(gc_context, root, avm.prototypes().movie_clip); root.set_depth(gc_context, 0); let mut levels = BTreeMap::new(); levels.insert(0, root); @@ -620,6 +619,8 @@ mod tests { load_manager: &mut LoadManager::new(), }; + root.post_instantiation(&mut avm, &mut context, root); + let object = ScriptObject::object(gc_context, Some(avm.prototypes().object)).into(); let globals = avm.global_object_cell(); diff --git a/core/src/avm1/test_utils.rs b/core/src/avm1/test_utils.rs index 412a7ab7e..307ba1845 100644 --- a/core/src/avm1/test_utils.rs +++ b/core/src/avm1/test_utils.rs @@ -26,7 +26,6 @@ where let mut avm = Avm1::new(gc_context, swf_version); let swf = Arc::new(SwfMovie::empty(swf_version)); let mut root: DisplayObject<'_> = MovieClip::new(swf_version, gc_context).into(); - root.post_instantiation(gc_context, root, avm.prototypes().movie_clip); root.set_depth(gc_context, 0); let mut levels = BTreeMap::new(); levels.insert(0, root); @@ -58,6 +57,7 @@ where player: None, load_manager: &mut LoadManager::new(), }; + root.post_instantiation(&mut avm, &mut context, root); let globals = avm.global_object_cell(); avm.insert_stack_frame(GcCell::allocate( diff --git a/core/src/display_object.rs b/core/src/display_object.rs index 0328efb5b..d1a9ee373 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -1,4 +1,4 @@ -use crate::avm1::{Object, TObject, Value}; +use crate::avm1::{Avm1, TObject, Value}; use crate::context::{RenderContext, UpdateContext}; use crate::player::NEWEST_PLAYER_VERSION; use crate::prelude::*; @@ -726,7 +726,7 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into> } } - fn run_frame(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) {} + fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext<'_, 'gc, '_>) {} fn render(&self, _context: &mut RenderContext<'_, 'gc>) {} fn unload(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) { self.set_removed(context.gc_context, true); @@ -823,9 +823,9 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into> fn post_instantiation( &mut self, - _gc_context: MutationContext<'gc, '_>, + _avm: &mut Avm1<'gc>, + _context: &mut UpdateContext<'_, 'gc, '_>, _display_object: DisplayObject<'gc>, - _proto: Object<'gc>, ) { } diff --git a/core/src/display_object/bitmap.rs b/core/src/display_object/bitmap.rs index 334dc92ff..8ad65f3d6 100644 --- a/core/src/display_object/bitmap.rs +++ b/core/src/display_object/bitmap.rs @@ -1,5 +1,6 @@ //! Bitmap display object +use crate::avm1::Avm1; use crate::backend::render::BitmapHandle; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; @@ -79,7 +80,7 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> { } } - fn run_frame(&mut self, _context: &mut UpdateContext) { + fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext) { // Noop } diff --git a/core/src/display_object/button.rs b/core/src/display_object/button.rs index 6061e690d..f93a2a3b2 100644 --- a/core/src/display_object/button.rs +++ b/core/src/display_object/button.rs @@ -1,4 +1,4 @@ -use crate::avm1::{Object, StageObject, Value}; +use crate::avm1::{Avm1, Object, StageObject, Value}; use crate::context::{ActionType, RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; use crate::events::{ButtonEvent, ButtonEventResult, ButtonKeyCode}; @@ -79,12 +79,13 @@ impl<'gc> Button<'gc> { pub fn handle_button_event( &mut self, + avm: &mut Avm1<'gc>, context: &mut crate::context::UpdateContext<'_, 'gc, '_>, event: ButtonEvent, ) { self.0 .write(context.gc_context) - .handle_button_event((*self).into(), context, event) + .handle_button_event((*self).into(), avm, context, event) } pub fn set_sounds(self, gc_context: MutationContext<'gc, '_>, sounds: swf::ButtonSounds) { @@ -128,21 +129,25 @@ impl<'gc> TDisplayObject<'gc> for Button<'gc> { fn post_instantiation( &mut self, - gc_context: MutationContext<'gc, '_>, + _avm: &mut Avm1<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, display_object: DisplayObject<'gc>, - proto: Object<'gc>, ) { - let mut mc = self.0.write(gc_context); + let mut mc = self.0.write(context.gc_context); if mc.object.is_none() { - let object = StageObject::for_display_object(gc_context, display_object, Some(proto)); + let object = StageObject::for_display_object( + context.gc_context, + display_object, + Some(context.system_prototypes.object), + ); mc.object = Some(object.into()); } } - fn run_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) { + fn run_frame(&mut self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) { self.0 .write(context.gc_context) - .run_frame((*self).into(), context) + .run_frame((*self).into(), avm, context) } fn render(&self, context: &mut RenderContext<'_, 'gc>) { @@ -222,6 +227,7 @@ impl<'gc> ButtonData<'gc> { fn set_state( &mut self, self_display_object: DisplayObject<'gc>, + avm: &mut Avm1<'gc>, context: &mut crate::context::UpdateContext<'_, 'gc, '_>, state: ButtonState, ) { @@ -237,8 +243,9 @@ impl<'gc> ButtonData<'gc> { if let Ok(mut child) = context .library .library_for_movie_mut(self.movie()) - .instantiate_by_id(record.id, context.gc_context, &context.system_prototypes) + .instantiate_by_id(record.id, context.gc_context) { + child.post_instantiation(avm, context, child); child.set_parent(context.gc_context, Some(self_display_object)); child.set_matrix(context.gc_context, &record.matrix.clone().into()); child.set_color_transform( @@ -255,25 +262,24 @@ impl<'gc> ButtonData<'gc> { fn run_frame( &mut self, self_display_object: DisplayObject<'gc>, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) { // TODO: Move this to post_instantiation. if !self.initialized { self.initialized = true; - self.set_state(self_display_object, context, ButtonState::Up); + self.set_state(self_display_object, avm, context, ButtonState::Up); for record in &self.static_data.read().records { if record.states.contains(&swf::ButtonState::HitTest) { match context .library .library_for_movie_mut(self.static_data.read().swf.clone()) - .instantiate_by_id( - record.id, - context.gc_context, - &context.system_prototypes, - ) { + .instantiate_by_id(record.id, context.gc_context) + { Ok(mut child) => { { + child.post_instantiation(avm, context, child); child.set_matrix(context.gc_context, &record.matrix.clone().into()); child.set_parent(context.gc_context, Some(self_display_object)); child.set_depth(context.gc_context, record.depth.into()); @@ -294,13 +300,14 @@ impl<'gc> ButtonData<'gc> { } for child in self.children.values_mut() { - child.run_frame(context); + child.run_frame(avm, context); } } fn handle_button_event( &mut self, self_display_object: DisplayObject<'gc>, + avm: &mut Avm1<'gc>, context: &mut crate::context::UpdateContext<'_, 'gc, '_>, event: ButtonEvent, ) { @@ -340,7 +347,7 @@ impl<'gc> ButtonData<'gc> { _ => (), } - self.set_state(self_display_object, context, new_state); + self.set_state(self_display_object, avm, context, new_state); } fn play_sound( diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index f4cbae62c..1f4de41cb 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -1,6 +1,6 @@ //! `EditText` display object and support code. use crate::avm1::globals::text_field::attach_virtual_properties; -use crate::avm1::{Object, StageObject, Value}; +use crate::avm1::{Avm1, Object, StageObject, Value}; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; use crate::font::{Font, Glyph, TextFormat}; @@ -419,7 +419,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> { Some(self.0.read().static_data.swf.clone()) } - fn run_frame(&mut self, _context: &mut UpdateContext) { + fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext) { // Noop } @@ -429,16 +429,20 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> { fn post_instantiation( &mut self, - gc_context: MutationContext<'gc, '_>, + _avm: &mut Avm1<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, display_object: DisplayObject<'gc>, - proto: Object<'gc>, ) { - let mut text = self.0.write(gc_context); + let mut text = self.0.write(context.gc_context); if text.object.is_none() { - let object = - StageObject::for_display_object(gc_context, display_object, Some(proto)).into(); + let object = StageObject::for_display_object( + context.gc_context, + display_object, + Some(context.system_prototypes.text_field), + ) + .into(); - attach_virtual_properties(gc_context, object); + attach_virtual_properties(context.gc_context, object); text.object = Some(object); } diff --git a/core/src/display_object/graphic.rs b/core/src/display_object/graphic.rs index dda856e5c..49ebb8741 100644 --- a/core/src/display_object/graphic.rs +++ b/core/src/display_object/graphic.rs @@ -1,3 +1,4 @@ +use crate::avm1::Avm1; use crate::backend::render::ShapeHandle; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; @@ -42,7 +43,18 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> { self.0.read().static_data.bounds.clone() } - fn run_frame(&mut self, _context: &mut UpdateContext) { + fn world_bounds(&self) -> BoundingBox { + // TODO: Use dirty flags and cache this. + let mut bounds = self.local_bounds(); + let mut node = self.parent(); + while let Some(display_object) = node { + bounds = bounds.transform(&*display_object.matrix()); + node = display_object.parent(); + } + bounds + } + + fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext) { // Noop } diff --git a/core/src/display_object/morph_shape.rs b/core/src/display_object/morph_shape.rs index 35c4da800..8dd3cde38 100644 --- a/core/src/display_object/morph_shape.rs +++ b/core/src/display_object/morph_shape.rs @@ -1,3 +1,4 @@ +use crate::avm1::Avm1; use crate::backend::render::{RenderBackend, ShapeHandle}; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; @@ -51,7 +52,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> { Some(*self) } - fn run_frame(&mut self, _context: &mut UpdateContext) { + fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext) { // Noop } diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index ef26b13d3..230190dfb 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -1,5 +1,5 @@ //! `MovieClip` display object and support code. -use crate::avm1::{Object, StageObject, Value}; +use crate::avm1::{Avm1, Object, StageObject, TObject, Value}; use crate::backend::audio::AudioStreamHandle; use crate::character::Character; use crate::context::{ActionType, RenderContext, UpdateContext}; @@ -41,6 +41,7 @@ pub struct MovieClipData<'gc> { object: Option>, clip_actions: SmallVec<[ClipAction; 2]>, flags: EnumSet, + avm1_constructor: Option>, } impl<'gc> MovieClip<'gc> { @@ -58,6 +59,7 @@ impl<'gc> MovieClip<'gc> { object: None, clip_actions: SmallVec::new(), flags: EnumSet::empty(), + avm1_constructor: None, }, )) } @@ -89,6 +91,7 @@ impl<'gc> MovieClip<'gc> { object: None, clip_actions: SmallVec::new(), flags: MovieClipFlags::Playing.into(), + avm1_constructor: None, }, )) } @@ -133,9 +136,9 @@ impl<'gc> MovieClip<'gc> { self.0.read().playing() } - pub fn next_frame(self, context: &mut UpdateContext<'_, 'gc, '_>) { + pub fn next_frame(self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) { if self.current_frame() < self.total_frames() { - self.goto_frame(context, self.current_frame() + 1, true); + self.goto_frame(avm, context, self.current_frame() + 1, true); } } @@ -143,9 +146,9 @@ impl<'gc> MovieClip<'gc> { self.0.write(context.gc_context).play() } - pub fn prev_frame(self, context: &mut UpdateContext<'_, 'gc, '_>) { + pub fn prev_frame(self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) { if self.current_frame() > 1 { - self.goto_frame(context, self.current_frame() - 1, true); + self.goto_frame(avm, context, self.current_frame() - 1, true); } } @@ -157,13 +160,14 @@ impl<'gc> MovieClip<'gc> { /// `frame` should be 1-based. pub fn goto_frame( self, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, frame: FrameNumber, stop: bool, ) { self.0 .write(context.gc_context) - .goto_frame(self.into(), context, frame, stop) + .goto_frame(self.into(), avm, context, frame, stop) } pub fn current_frame(self) -> FrameNumber { @@ -179,6 +183,14 @@ impl<'gc> MovieClip<'gc> { self.0.read().static_data.total_frames } + pub fn set_avm1_constructor( + self, + gc_context: MutationContext<'gc, '_>, + prototype: Option>, + ) { + self.0.write(gc_context).avm1_constructor = prototype; + } + pub fn frame_label_to_number(self, frame_label: &str) -> Option { // Frame labels are case insensitive. let label = frame_label.to_ascii_lowercase(); @@ -314,10 +326,10 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { Some(self.0.read().movie()) } - fn run_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) { + fn run_frame(&mut self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) { // Children must run first. for mut child in self.children() { - child.run_frame(context); + child.run_frame(avm, context); } // Run my load/enterFrame clip event. @@ -332,7 +344,7 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { // Run my SWF tags. if mc.playing() { - mc.run_frame_internal((*self).into(), context, true); + mc.run_frame_internal((*self).into(), avm, context, true); } if is_load_frame { @@ -385,13 +397,37 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { fn post_instantiation( &mut self, - gc_context: MutationContext<'gc, '_>, + avm: &mut Avm1<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, display_object: DisplayObject<'gc>, - proto: Object<'gc>, ) { - let mut mc = self.0.write(gc_context); + let mut mc = self.0.write(context.gc_context); if mc.object.is_none() { - let object = StageObject::for_display_object(gc_context, display_object, Some(proto)); + if let Some(constructor) = mc.avm1_constructor { + if let Ok(prototype) = constructor + .get("prototype", avm, context) + .and_then(|v| v.resolve(avm, context)) + .and_then(|v| v.as_object()) + { + let object: Object<'gc> = StageObject::for_display_object( + context.gc_context, + display_object, + Some(prototype), + ) + .into(); + mc.object = Some(object); + if let Ok(result) = constructor.call(avm, context, object, &[]) { + let _ = result.resolve(avm, context); + } + return; + } + }; + + let object = StageObject::for_display_object( + context.gc_context, + display_object, + Some(context.system_prototypes.movie_clip), + ); mc.object = Some(object.into()); } } @@ -427,6 +463,7 @@ unsafe impl<'gc> Collect for MovieClipData<'gc> { self.base.trace(cc); self.static_data.trace(cc); self.object.trace(cc); + self.avm1_constructor.trace(cc); } } @@ -521,6 +558,7 @@ impl<'gc> MovieClipData<'gc> { pub fn goto_frame( &mut self, self_display_object: DisplayObject<'gc>, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, mut frame: FrameNumber, stop: bool, @@ -538,13 +576,14 @@ impl<'gc> MovieClipData<'gc> { } if frame != self.current_frame() { - self.run_goto(self_display_object, context, frame); + self.run_goto(self_display_object, avm, context, frame); } } fn run_frame_internal( &mut self, self_display_object: DisplayObject<'gc>, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, run_display_actions: bool, ) { @@ -555,7 +594,7 @@ impl<'gc> MovieClipData<'gc> { // Looping acts exactly like a gotoAndPlay(1). // Specifically, object that existed on frame 1 should not be destroyed // and recreated. - self.run_goto(self_display_object, context, 1); + self.run_goto(self_display_object, avm, context, 1); return; } else { // Single frame clips do not play. @@ -571,16 +610,16 @@ impl<'gc> MovieClipData<'gc> { let tag_callback = |reader: &mut _, tag_code, tag_len| match tag_code { TagCode::DoAction => self.do_action(self_display_object, context, reader, tag_len), TagCode::PlaceObject if run_display_actions => { - self.place_object(self_display_object, context, reader, tag_len, 1) + self.place_object(self_display_object, avm, context, reader, tag_len, 1) } TagCode::PlaceObject2 if run_display_actions => { - self.place_object(self_display_object, context, reader, tag_len, 2) + self.place_object(self_display_object, avm, context, reader, tag_len, 2) } TagCode::PlaceObject3 if run_display_actions => { - self.place_object(self_display_object, context, reader, tag_len, 3) + self.place_object(self_display_object, avm, context, reader, tag_len, 3) } TagCode::PlaceObject4 if run_display_actions => { - self.place_object(self_display_object, context, reader, tag_len, 4) + self.place_object(self_display_object, avm, context, reader, tag_len, 4) } TagCode::RemoveObject if run_display_actions => self.remove_object(context, reader, 1), TagCode::RemoveObject2 if run_display_actions => self.remove_object(context, reader, 2), @@ -602,9 +641,11 @@ impl<'gc> MovieClipData<'gc> { } } + #[allow(clippy::too_many_arguments)] fn instantiate_child( &mut self, self_display_object: DisplayObject<'gc>, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, id: CharacterId, depth: Depth, @@ -614,8 +655,10 @@ impl<'gc> MovieClipData<'gc> { if let Ok(mut child) = context .library .library_for_movie_mut(self.movie()) - .instantiate_by_id(id, context.gc_context, &context.system_prototypes) + .instantiate_by_id(id, context.gc_context) { + child.post_instantiation(avm, context, child); + // Remove previous child from children list, // and add new childonto front of the list. let prev_child = self.children.insert(depth, child); @@ -635,7 +678,7 @@ impl<'gc> MovieClipData<'gc> { } // Run first frame. child.apply_place_object(context.gc_context, place_object); - child.run_frame(context); + child.run_frame(avm, context); } Some(child) } else { @@ -684,6 +727,7 @@ impl<'gc> MovieClipData<'gc> { pub fn run_goto( &mut self, self_display_object: DisplayObject<'gc>, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, frame: FrameNumber, ) { @@ -786,6 +830,7 @@ impl<'gc> MovieClipData<'gc> { // Run the list of goto commands to actually create and update the display objects. let run_goto_command = |clip: &mut MovieClipData<'gc>, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, params: &GotoPlaceObject| { let child_entry = clip.children.get_mut(¶ms.depth()).copied(); @@ -803,6 +848,7 @@ impl<'gc> MovieClipData<'gc> { _ => { if let Some(mut child) = clip.instantiate_child( self_display_object, + avm, context, params.id(), params.depth(), @@ -828,7 +874,7 @@ impl<'gc> MovieClipData<'gc> { goto_commands .iter() .filter(|params| params.frame < frame) - .for_each(|goto| run_goto_command(self, context, goto)); + .for_each(|goto| run_goto_command(self, avm, context, goto)); // Next, run the final frame for the parent clip. // Re-run the final frame without display tags (DoAction, StartSound, etc.) @@ -837,7 +883,7 @@ impl<'gc> MovieClipData<'gc> { if hit_target_frame { self.current_frame -= 1; self.tag_stream_pos = frame_pos; - self.run_frame_internal(self_display_object, context, false); + self.run_frame_internal(self_display_object, avm, context, false); } else { self.current_frame = clamped_frame; } @@ -846,7 +892,7 @@ impl<'gc> MovieClipData<'gc> { goto_commands .iter() .filter(|params| params.frame >= frame) - .for_each(|goto| run_goto_command(self, context, goto)); + .for_each(|goto| run_goto_command(self, avm, context, goto)); } /// Handles a PlaceObject tag when running a goto action. @@ -1816,6 +1862,7 @@ impl<'gc, 'a> MovieClipData<'gc> { fn place_object( &mut self, self_display_object: DisplayObject<'gc>, + avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, reader: &mut SwfStream<&'a [u8]>, tag_len: usize, @@ -1831,6 +1878,7 @@ impl<'gc, 'a> MovieClipData<'gc> { PlaceObjectAction::Place(id) | PlaceObjectAction::Replace(id) => { if let Some(child) = self.instantiate_child( self_display_object, + avm, context, id, place_object.depth.into(), diff --git a/core/src/display_object/text.rs b/core/src/display_object/text.rs index 048b49da6..08ec933a6 100644 --- a/core/src/display_object/text.rs +++ b/core/src/display_object/text.rs @@ -1,3 +1,4 @@ +use crate::avm1::Avm1; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; use crate::prelude::*; @@ -52,7 +53,7 @@ impl<'gc> TDisplayObject<'gc> for Text<'gc> { Some(self.0.read().static_data.swf.clone()) } - fn run_frame(&mut self, _context: &mut UpdateContext) { + fn run_frame(&mut self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext) { // Noop } diff --git a/core/src/library.rs b/core/src/library.rs index 6e1a1a43e..d8e03a5da 100644 --- a/core/src/library.rs +++ b/core/src/library.rs @@ -1,5 +1,3 @@ -use crate::avm1::globals::SystemPrototypes; -use crate::avm1::Object; use crate::backend::audio::SoundHandle; use crate::character::Character; use crate::display_object::TDisplayObject; @@ -79,14 +77,14 @@ impl<'gc> MovieLibrary<'gc> { } /// Instantiates the library item with the given character ID into a display object. + /// The object must then be post-instantiated before being used. pub fn instantiate_by_id( &self, id: CharacterId, gc_context: MutationContext<'gc, '_>, - prototypes: &SystemPrototypes<'gc>, ) -> Result, Box> { if let Some(character) = self.characters.get(&id) { - self.instantiate_display_object(character, gc_context, prototypes) + self.instantiate_display_object(character, gc_context) } else { log::error!("Tried to instantiate non-registered character ID {}", id); Err("Character id doesn't exist".into()) @@ -94,14 +92,14 @@ impl<'gc> MovieLibrary<'gc> { } /// Instantiates the library item with the given export name into a display object. + /// The object must then be post-instantiated before being used. pub fn instantiate_by_export_name( &self, export_name: &str, gc_context: MutationContext<'gc, '_>, - prototypes: &SystemPrototypes<'gc>, ) -> Result, Box> { if let Some(character) = self.export_characters.get(export_name) { - self.instantiate_display_object(character, gc_context, prototypes) + self.instantiate_display_object(character, gc_context) } else { log::error!( "Tried to instantiate non-registered character {}", @@ -112,30 +110,22 @@ impl<'gc> MovieLibrary<'gc> { } /// Instantiates the given character into a display object. + /// The object must then be post-instantiated before being used. fn instantiate_display_object( &self, character: &Character<'gc>, gc_context: MutationContext<'gc, '_>, - prototypes: &SystemPrototypes<'gc>, ) -> Result, Box> { - let (mut obj, proto): (DisplayObject<'gc>, Object<'gc>) = match character { - Character::Bitmap(bitmap) => (bitmap.instantiate(gc_context), prototypes.object), - Character::EditText(edit_text) => { - (edit_text.instantiate(gc_context), prototypes.text_field) - } - Character::Graphic(graphic) => (graphic.instantiate(gc_context), prototypes.object), - Character::MorphShape(morph_shape) => { - (morph_shape.instantiate(gc_context), prototypes.object) - } - Character::MovieClip(movie_clip) => { - (movie_clip.instantiate(gc_context), prototypes.movie_clip) - } - Character::Button(button) => (button.instantiate(gc_context), prototypes.object), - Character::Text(text) => (text.instantiate(gc_context), prototypes.object), - _ => return Err("Not a DisplayObject".into()), - }; - obj.post_instantiation(gc_context, obj, proto); - Ok(obj) + match character { + Character::Bitmap(bitmap) => Ok(bitmap.instantiate(gc_context)), + Character::EditText(edit_text) => Ok(edit_text.instantiate(gc_context)), + Character::Graphic(graphic) => Ok(graphic.instantiate(gc_context)), + Character::MorphShape(morph_shape) => Ok(morph_shape.instantiate(gc_context)), + Character::MovieClip(movie_clip) => Ok(movie_clip.instantiate(gc_context)), + Character::Button(button) => Ok(button.instantiate(gc_context)), + Character::Text(text) => Ok(text.instantiate(gc_context)), + _ => Err("Not a DisplayObject".into()), + } } pub fn get_font(&self, id: CharacterId) -> Option> { diff --git a/core/src/loader.rs b/core/src/loader.rs index 8ff4ff739..72a1fcd48 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -334,7 +334,7 @@ impl<'gc> Loader<'gc> { .expect("Attempted to load movie into not movie clip"); mc.replace_with_movie(uc.gc_context, Some(movie.clone())); - mc.post_instantiation(uc.gc_context, clip, avm.prototypes().movie_clip); + mc.post_instantiation(avm, uc, clip); let mut morph_shapes = fnv::FnvHashMap::default(); mc.preload(uc, &mut morph_shapes); diff --git a/core/src/player.rs b/core/src/player.rs index 809a481ab..73a30d08a 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -193,9 +193,6 @@ impl Player { }; let mut library = Library::default(); - let root = MovieClip::from_movie(gc_context, movie.clone()).into(); - let mut levels = BTreeMap::new(); - levels.insert(0, root); library .library_for_movie_mut(movie.clone()) @@ -205,7 +202,7 @@ impl Player { gc_context, GcRootData { library, - levels, + levels: BTreeMap::new(), mouse_hovered_object: None, drag_object: None, avm: Avm1::new(gc_context, NEWEST_PLAYER_VERSION), @@ -236,14 +233,12 @@ impl Player { self_reference: None, }; - player.gc_arena.mutate(|gc_context, gc_root| { - let mut root_data = gc_root.0.write(gc_context); - let mc_proto = root_data.avm.prototypes().movie_clip; - - for (_i, level) in root_data.levels.iter_mut() { - level.post_instantiation(gc_context, *level, mc_proto); - level.set_depth(gc_context, 0); - } + player.mutate_with_update_context(|avm, context| { + let mut root: DisplayObject = + MovieClip::from_movie(context.gc_context, movie.clone()).into(); + root.post_instantiation(avm, context, root); + root.set_depth(context.gc_context, 0); + context.levels.insert(0, root); }); player.build_matrices(); @@ -430,13 +425,13 @@ impl Player { PlayerEvent::MouseDown { .. } => { is_mouse_down = true; needs_render = true; - button.handle_button_event(context, ButtonEvent::Press); + button.handle_button_event(avm, context, ButtonEvent::Press); } PlayerEvent::MouseUp { .. } => { is_mouse_down = false; needs_render = true; - button.handle_button_event(context, ButtonEvent::Release); + button.handle_button_event(avm, context, ButtonEvent::Release); } _ => (), @@ -508,7 +503,7 @@ impl Player { // RollOut of previous node. if let Some(node) = cur_hovered { if let Some(mut button) = node.as_button() { - button.handle_button_event(context, ButtonEvent::RollOut); + button.handle_button_event(avm, context, ButtonEvent::RollOut); } } @@ -516,7 +511,7 @@ impl Player { new_cursor = MouseCursor::Arrow; if let Some(node) = new_hovered { if let Some(mut button) = node.as_button() { - button.handle_button_event(context, ButtonEvent::RollOver); + button.handle_button_event(avm, context, ButtonEvent::RollOver); new_cursor = MouseCursor::Hand; } } @@ -563,7 +558,7 @@ impl Player { } pub fn run_frame(&mut self) { - self.update(|_avm, update_context| { + self.update(|avm, update_context| { // TODO: In what order are levels run? // NOTE: We have to copy all the layer pointers into a separate list // because level updates can create more levels, which we don't @@ -571,7 +566,7 @@ impl Player { let levels: Vec<_> = update_context.levels.values().copied().collect(); for mut level in levels { - level.run_frame(update_context); + level.run_frame(avm, update_context); } }) } diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 391b9e3c1..1917dc33a 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -127,6 +127,7 @@ swf_tests! { (equals2_swf5, "avm1/equals2_swf5", 1), (equals2_swf6, "avm1/equals2_swf6", 1), (equals2_swf7, "avm1/equals2_swf7", 1), + (register_class, "avm1/register_class", 1), (set_variable_scope, "avm1/set_variable_scope", 1), (slash_syntax, "avm1/slash_syntax", 2), (strictequals_swf6, "avm1/strictequals_swf6", 1), diff --git a/core/tests/swfs/avm1/attach_movie/output.txt b/core/tests/swfs/avm1/attach_movie/output.txt index 1c7a52b34..46fe527c7 100644 --- a/core/tests/swfs/avm1/attach_movie/output.txt +++ b/core/tests/swfs/avm1/attach_movie/output.txt @@ -1,15 +1,51 @@ +// mc = _root.attachMovie("Clip", "clip", 0) +// clip _level0.clip + +// clip == mc true + +// mc._x 0 + +// mc._y 0 + +// mc = _root.attachMovie("BOGUS", "clip", 0) +// mc undefined + +// clip == mc false + +// mc = _root.attachMovie("Clip", "clip", 0, {_x: 100, _y: 50, foo: "foo"}) +// clip == mc true + +// mc._x 100 + +// mc._y 50 + +// mc.foo foo + +// mc = _root.attachMovie("Clip", "clip", -1) +// clip == mc true + +// mc._x 0 + +// mc._y 0 + +// mc2 = _root.attachMovie("Clip", "clip2", 2130690044) +// mc2 _level0.clip2 + +// mc3 = _root.attachMovie("Clip", "clip2", 2130690045) +// mc3 undefined + diff --git a/core/tests/swfs/avm1/attach_movie/test.fla b/core/tests/swfs/avm1/attach_movie/test.fla index 1e0ffb16e..046420b8e 100644 Binary files a/core/tests/swfs/avm1/attach_movie/test.fla and b/core/tests/swfs/avm1/attach_movie/test.fla differ diff --git a/core/tests/swfs/avm1/attach_movie/test.swf b/core/tests/swfs/avm1/attach_movie/test.swf index 20911dab2..01d98936c 100644 Binary files a/core/tests/swfs/avm1/attach_movie/test.swf and b/core/tests/swfs/avm1/attach_movie/test.swf differ diff --git a/core/tests/swfs/avm1/register_class/output.txt b/core/tests/swfs/avm1/register_class/output.txt new file mode 100644 index 000000000..ce14b8315 --- /dev/null +++ b/core/tests/swfs/avm1/register_class/output.txt @@ -0,0 +1,18 @@ +Custom movieclip! +// typeof custom_movieclip +movieclip +// custom_movieclip.__proto__ == custom_mc.prototype +true +// custom_movieclip.__proto__ == MovieClip.prototype +false +// custom_movieclip.isCustom() +true + +// typeof normal_movieclip +movieclip +// normal_movieclip.__proto__ == custom_mc.prototype +false +// normal_movieclip.__proto__ == MovieClip.prototype +true +// normal_movieclip.isCustom() +undefined diff --git a/core/tests/swfs/avm1/register_class/test.fla b/core/tests/swfs/avm1/register_class/test.fla new file mode 100644 index 000000000..dcb0c56f1 Binary files /dev/null and b/core/tests/swfs/avm1/register_class/test.fla differ diff --git a/core/tests/swfs/avm1/register_class/test.swf b/core/tests/swfs/avm1/register_class/test.swf new file mode 100644 index 000000000..1794ba64c Binary files /dev/null and b/core/tests/swfs/avm1/register_class/test.swf differ