From 1fbf1cfaacce7cd9b2d3b5b9595c6e21adf38c42 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Tue, 26 Nov 2024 12:09:19 -0800 Subject: [PATCH 1/6] core/avm2: Allow for DO->AVM2 EditText linkage, remove AVM2->DO EditText linkage --- .../src/avm2/globals/flash/text/text_field.rs | 36 +++---------------- core/src/display_object/edit_text.rs | 26 ++++++++++---- core/src/display_object/movie_clip.rs | 2 ++ 3 files changed, 26 insertions(+), 38 deletions(-) diff --git a/core/src/avm2/globals/flash/text/text_field.rs b/core/src/avm2/globals/flash/text/text_field.rs index 01b2995d7aa2..d91905534c1c 100644 --- a/core/src/avm2/globals/flash/text/text_field.rs +++ b/core/src/avm2/globals/flash/text/text_field.rs @@ -17,39 +17,11 @@ pub fn text_field_allocator<'gc>( class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let textfield_cls = activation - .avm2() - .classes() - .textfield - .inner_class_definition(); - - let mut class_def = Some(class.inner_class_definition()); - let orig_class = class; - while let Some(class) = class_def { - if class == textfield_cls { - let movie = activation.caller_movie_or_root(); - let display_object = - EditText::new(activation.context, movie, 0.0, 0.0, 100.0, 100.0).into(); - return initialize_for_allocator(activation, display_object, orig_class); - } + // Creating a TextField from AS ignores SymbolClass linkage. + let movie = activation.caller_movie_or_root(); + let display_object = EditText::new(activation.context, movie, 0.0, 0.0, 100.0, 100.0).into(); - if let Some((movie, symbol)) = activation - .context - .library - .avm2_class_registry() - .class_symbol(class) - { - let child = activation - .context - .library - .library_for_movie_mut(movie) - .instantiate_by_id(symbol, activation.context.gc_context)?; - - return initialize_for_allocator(activation, child, orig_class); - } - class_def = class.super_class(); - } - unreachable!("A TextField subclass should have TextField in superclass chain"); + initialize_for_allocator(activation, display_object, class) } pub fn get_always_show_selection<'gc>( diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index 81039fc02063..23a055206452 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -9,8 +9,8 @@ use crate::avm1::{ }; use crate::avm2::Avm2; use crate::avm2::{ - Activation as Avm2Activation, EventObject as Avm2EventObject, Object as Avm2Object, - StageObject as Avm2StageObject, TObject as _, + Activation as Avm2Activation, ClassObject as Avm2ClassObject, EventObject as Avm2EventObject, + Object as Avm2Object, StageObject as Avm2StageObject, TObject as _, }; use crate::backend::ui::MouseCursor; use crate::context::{RenderContext, UpdateContext}; @@ -130,6 +130,9 @@ pub struct EditTextData<'gc> { /// The display object that the variable binding is bound to. bound_stage_object: Option>, + /// The AVM2 class of this button. If None, it is flash.text.TextField. + class: Option>, + /// The selected portion of the text, or None if the text is not selected. /// Note: Selections work differently in AVM1, AVM2, and Ruffle. /// @@ -317,6 +320,7 @@ impl<'gc> EditText<'gc> { requested_height: swf_tag.bounds().height(), variable: variable.map(|s| s.to_string_lossy(encoding)), bound_stage_object: None, + class: None, selection, render_settings: Default::default(), hscroll: 0.0, @@ -1988,20 +1992,26 @@ impl<'gc> EditText<'gc> { context: &mut UpdateContext<'gc>, display_object: DisplayObject<'gc>, ) { - let textfield_constr = context.avm2.classes().textfield; + let class_object = self + .0 + .read() + .class + .unwrap_or_else(|| context.avm2.classes().textfield); + let mut activation = Avm2Activation::from_nothing(context); match Avm2StageObject::for_display_object_childless( &mut activation, display_object, - textfield_constr, + class_object, ) { Ok(object) => { let object: Avm2Object<'gc> = object.into(); - self.0.write(activation.context.gc_context).object = Some(object.into()) + + self.0.write(activation.gc()).object = Some(object.into()); } Err(e) => tracing::error!( - "Got {} when constructing AVM2 side of dynamic text field", + "Got error when constructing AVM2 side of dynamic text field: {}", e ), } @@ -2267,6 +2277,10 @@ impl<'gc> EditText<'gc> { let new_selection = TextSelection::span_across(first_selection, current_selection); self.set_selection(Some(new_selection), context.gc()); } + + pub fn set_avm2_class(self, mc: &Mutation<'gc>, class: Avm2ClassObject<'gc>) { + self.0.write(mc).class = Some(class); + } } impl<'gc> TDisplayObject<'gc> for EditText<'gc> { diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 5420a57b59e9..5cb7705a0edd 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -4562,6 +4562,8 @@ impl<'gc, 'a> MovieClip<'gc> { .library_for_movie_mut(movie.clone()); match library.character_by_id(id) { + Some(Character::EditText(edit_text)) => edit_text + .set_avm2_class(activation.context.gc_context, class_object), Some(Character::MovieClip(mc)) => { mc.set_avm2_class(activation.context.gc_context, Some(class_object)) } From e93d2e30cebd84b7bc56f53fd688cff7cb62f102 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Tue, 26 Nov 2024 12:21:20 -0800 Subject: [PATCH 2/6] core/avm2: Allow for DO->AVM2 Graphic linkage, remove AVM2->DO Graphic linkage --- core/src/avm2/globals/flash/display/shape.rs | 28 ++------------------ core/src/display_object/graphic.rs | 21 ++++++++++++--- core/src/display_object/movie_clip.rs | 3 +++ 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/core/src/avm2/globals/flash/display/shape.rs b/core/src/avm2/globals/flash/display/shape.rs index 76dcdff6bb4e..3d3f32041d6d 100644 --- a/core/src/avm2/globals/flash/display/shape.rs +++ b/core/src/avm2/globals/flash/display/shape.rs @@ -12,33 +12,9 @@ pub fn shape_allocator<'gc>( class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let shape_cls = activation.avm2().classes().shape.inner_class_definition(); + let display_object = Graphic::empty(activation.context).into(); - let mut class_def = Some(class.inner_class_definition()); - let orig_class = class; - while let Some(class) = class_def { - if class == shape_cls { - let display_object = Graphic::empty(activation.context).into(); - return initialize_for_allocator(activation, display_object, orig_class); - } - - if let Some((movie, symbol)) = activation - .context - .library - .avm2_class_registry() - .class_symbol(class) - { - let child = activation - .context - .library - .library_for_movie_mut(movie) - .instantiate_by_id(symbol, activation.context.gc_context)?; - - return initialize_for_allocator(activation, child, orig_class); - } - class_def = class.super_class(); - } - unreachable!("A Shape subclass should have Shape in superclass chain"); + initialize_for_allocator(activation, display_object, class) } /// Implements `graphics`. diff --git a/core/src/display_object/graphic.rs b/core/src/display_object/graphic.rs index db34683f383e..bae4211e6d3b 100644 --- a/core/src/display_object/graphic.rs +++ b/core/src/display_object/graphic.rs @@ -1,6 +1,7 @@ use crate::avm1::Object as Avm1Object; use crate::avm2::{ - Activation as Avm2Activation, Object as Avm2Object, StageObject as Avm2StageObject, + Activation as Avm2Activation, ClassObject as Avm2ClassObject, Object as Avm2Object, + StageObject as Avm2StageObject, }; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, DisplayObjectPtr}; @@ -33,6 +34,7 @@ impl fmt::Debug for Graphic<'_> { pub struct GraphicData<'gc> { base: DisplayObjectBase<'gc>, static_data: gc_arena::Gc<'gc, GraphicStatic>, + class: Option>, avm2_object: Option>, /// This is lazily allocated on demand, to make `GraphicData` smaller in the common case. #[collect(require_static)] @@ -64,6 +66,7 @@ impl<'gc> Graphic<'gc> { GraphicData { base: Default::default(), static_data: gc_arena::Gc::new(context.gc_context, static_data), + class: None, avm2_object: None, drawing: None, }, @@ -96,6 +99,7 @@ impl<'gc> Graphic<'gc> { GraphicData { base: Default::default(), static_data: gc_arena::Gc::new(context.gc_context, static_data), + class: None, avm2_object: None, drawing: None, }, @@ -107,6 +111,10 @@ impl<'gc> Graphic<'gc> { &mut **w.drawing.get_or_insert_with(Default::default) }) } + + pub fn set_avm2_class(self, mc: &Mutation<'gc>, class: Avm2ClassObject<'gc>) { + self.0.write(mc).class = Some(class); + } } impl<'gc> TDisplayObject<'gc> for Graphic<'gc> { @@ -140,19 +148,24 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> { fn construct_frame(&self, context: &mut UpdateContext<'gc>) { if self.movie().is_action_script_3() && matches!(self.object2(), Avm2Value::Null) { - let shape_constr = context.avm2.classes().shape; + let class_object = self + .0 + .read() + .class + .unwrap_or_else(|| context.avm2.classes().shape); + let mut activation = Avm2Activation::from_nothing(context); match Avm2StageObject::for_display_object_childless( &mut activation, (*self).into(), - shape_constr, + class_object, ) { Ok(object) => { self.0.write(activation.context.gc_context).avm2_object = Some(object.into()) } Err(e) => { - tracing::error!("Got {} when constructing AVM2 side of display object", e) + tracing::error!("Got error when constructing AVM2 side of shape: {}", e) } } diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 5cb7705a0edd..dd390458da33 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -4564,6 +4564,9 @@ impl<'gc, 'a> MovieClip<'gc> { match library.character_by_id(id) { Some(Character::EditText(edit_text)) => edit_text .set_avm2_class(activation.context.gc_context, class_object), + Some(Character::Graphic(graphic)) => { + graphic.set_avm2_class(activation.context.gc_context, class_object) + } Some(Character::MovieClip(mc)) => { mc.set_avm2_class(activation.context.gc_context, Some(class_object)) } From 7ba203df9c7407261c2a59afe2827bd7d120b2a3 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Mon, 25 Nov 2024 09:58:00 -0800 Subject: [PATCH 3/6] core/avm2: Move logic for looking up ClassObject for SymbolClass to AVM2 Also ensure that any ClassObject linked to a DO extends `flash.display.DisplayObject` --- core/src/avm2.rs | 69 ++++++++++++++++++++++++++- core/src/avm2/class.rs | 14 ++++-- core/src/avm2/error.rs | 24 ++++++---- core/src/avm2/globals.rs | 8 +++- core/src/avm2/globals/flash/net.rs | 13 ++--- core/src/avm2/verify.rs | 37 +++++++++++--- core/src/display_object/movie_clip.rs | 38 +++++++-------- 7 files changed, 150 insertions(+), 53 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 9840c033dac9..301010a905f2 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -3,15 +3,16 @@ use std::rc::Rc; use crate::avm2::class::{AllocatorFn, CustomConstructorFn}; -use crate::avm2::error::make_error_1107; +use crate::avm2::error::{make_error_1014, make_error_1107, type_error, Error1014Type}; use crate::avm2::globals::{ init_builtin_system_classes, init_native_system_classes, SystemClassDefs, SystemClasses, }; use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::scope::ScopeChain; use crate::avm2::script::{Script, TranslationUnit}; +use crate::character::Character; use crate::context::UpdateContext; -use crate::display_object::{DisplayObject, DisplayObjectWeak, TDisplayObject}; +use crate::display_object::{DisplayObject, DisplayObjectWeak, MovieClip, TDisplayObject}; use crate::string::{AvmString, StringContext}; use crate::tag_utils::SwfMovie; use crate::PlayerRuntime; @@ -579,6 +580,70 @@ impl<'gc> Avm2<'gc> { Ok(()) } + pub fn lookup_class_for_character( + activation: &mut Activation<'_, 'gc>, + movie_clip: MovieClip<'gc>, + domain: Domain<'gc>, + name: QName<'gc>, + id: u16, + ) -> Result, Error<'gc>> { + let movie = movie_clip.movie().clone(); + + let class_object = domain + .get_defined_value(activation, name)? + .as_object() + .and_then(|o| o.as_class_object()) + .ok_or_else(|| { + make_error_1014( + activation, + Error1014Type::ReferenceError, + name.to_qualified_name(activation.gc()), + ) + })?; + + let class = class_object.inner_class_definition(); + + let library = activation.context.library.library_for_movie_mut(movie); + let character = library.character_by_id(id); + + if let Some(character) = character { + if matches!( + character, + Character::EditText(_) + | Character::Graphic(_) + | Character::MovieClip(_) + | Character::Avm2Button(_) + ) { + // The class must extend DisplayObject to ensure that events + // can properly be dispatched to them + if !class.has_class_in_chain(activation.avm2().class_defs().display_object) { + return Err(Error::AvmError(type_error( + activation, + &format!("Error #2022: Class {}$ must inherit from DisplayObject to link to a symbol.", name.to_qualified_name(activation.gc())), + 2022, + )?)); + } + } + } else if movie_clip.avm2_class().is_none() { + // If this ID doesn't correspond to any character, and the MC that + // we're processing doesn't have an AVM2 class set, then this + // ClassObject is going to be the class of the MC. Ensure it + // subclasses Sprite. + if !class.has_class_in_chain(activation.avm2().class_defs().sprite) { + return Err(Error::AvmError(type_error( + activation, + &format!( + "Error #2023: Class {}$ must inherit from Sprite to link to the root.", + name.to_qualified_name(activation.gc()) + ), + 2023, + )?)); + } + } + + Ok(class_object) + } + /// Load an ABC file embedded in a `DoAbc` or `DoAbc2` tag. pub fn do_abc( context: &mut UpdateContext<'gc>, diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 4b4682e97e10..90a709e29121 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -1,7 +1,7 @@ //! AVM2 classes use crate::avm2::activation::Activation; -use crate::avm2::error::make_error_1014; +use crate::avm2::error::{make_error_1014, Error1014Type}; use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::object::{scriptobject_allocator, ClassObject, Object}; use crate::avm2::script::TranslationUnit; @@ -448,7 +448,11 @@ impl<'gc> Class<'gc> { .domain() .get_class(activation.context, &multiname) .ok_or_else(|| { - make_error_1014(activation, multiname.to_qualified_name(activation.gc())) + make_error_1014( + activation, + Error1014Type::VerifyError, + multiname.to_qualified_name(activation.gc()), + ) })?, ) }; @@ -468,7 +472,11 @@ impl<'gc> Class<'gc> { .domain() .get_class(activation.context, &multiname) .ok_or_else(|| { - make_error_1014(activation, multiname.to_qualified_name(activation.gc())) + make_error_1014( + activation, + Error1014Type::VerifyError, + multiname.to_qualified_name(activation.gc()), + ) })?, ); } diff --git a/core/src/avm2/error.rs b/core/src/avm2/error.rs index d391b0a40759..6b90a3326419 100644 --- a/core/src/avm2/error.rs +++ b/core/src/avm2/error.rs @@ -193,18 +193,23 @@ pub fn make_error_1010<'gc>( } } +pub enum Error1014Type { + ReferenceError, + VerifyError, +} + #[inline(never)] #[cold] pub fn make_error_1014<'gc>( activation: &mut Activation<'_, 'gc>, + kind: Error1014Type, class_name: AvmString<'gc>, ) -> Error<'gc> { - let err = verify_error( - activation, - &format!("Error #1014: Class {} could not be found.", class_name), - 1014, - ); - + let message = &format!("Error #1014: Class {} could not be found.", class_name); + let err = match kind { + Error1014Type::ReferenceError => reference_error(activation, message, 1014), + Error1014Type::VerifyError => verify_error(activation, message, 1014), + }; match err { Ok(err) => Error::AvmError(err), Err(err) => err, @@ -505,11 +510,10 @@ pub fn make_error_2004<'gc>( kind: Error2004Type, ) -> Error<'gc> { let message = "Error #2004: One of the parameters is invalid."; - let code = 2004; let err = match kind { - Error2004Type::Error => error(activation, message, code), - Error2004Type::ArgumentError => argument_error(activation, message, code), - Error2004Type::TypeError => type_error(activation, message, code), + Error2004Type::Error => error(activation, message, 2004), + Error2004Type::ArgumentError => argument_error(activation, message, 2004), + Error2004Type::TypeError => type_error(activation, message, 2004), }; match err { Ok(err) => Error::AvmError(err), diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 7c4d6ca849b4..11d563668d76 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -205,9 +205,10 @@ pub struct SystemClassDefs<'gc> { pub graphicssolidfill: Class<'gc>, pub graphicsshaderfill: Class<'gc>, pub graphicsstroke: Class<'gc>, - pub cubetexture: Class<'gc>, pub rectangletexture: Class<'gc>, + pub display_object: Class<'gc>, + pub sprite: Class<'gc>, } impl<'gc> SystemClasses<'gc> { @@ -373,9 +374,10 @@ impl<'gc> SystemClassDefs<'gc> { graphicssolidfill: object, graphicsshaderfill: object, graphicsstroke: object, - cubetexture: object, rectangletexture: object, + display_object: object, + sprite: object, } } } @@ -975,6 +977,7 @@ pub fn init_native_system_classes(activation: &mut Activation<'_, '_>) { [ ("flash.display", "Bitmap", bitmap), ("flash.display", "BitmapData", bitmapdata), + ("flash.display", "DisplayObject", display_object), ("flash.display", "IGraphicsData", igraphicsdata), ("flash.display", "GraphicsBitmapFill", graphicsbitmapfill), ("flash.display", "GraphicsEndFill", graphicsendfill), @@ -991,6 +994,7 @@ pub fn init_native_system_classes(activation: &mut Activation<'_, '_>) { ), ("flash.display", "GraphicsSolidFill", graphicssolidfill), ("flash.display", "GraphicsStroke", graphicsstroke), + ("flash.display", "Sprite", sprite), ("flash.display3D.textures", "CubeTexture", cubetexture), ( "flash.display3D.textures", diff --git a/core/src/avm2/globals/flash/net.rs b/core/src/avm2/globals/flash/net.rs index 8f100c5c945d..c95b48f6b6f7 100644 --- a/core/src/avm2/globals/flash/net.rs +++ b/core/src/avm2/globals/flash/net.rs @@ -1,6 +1,6 @@ //! `flash.net` namespace -use crate::avm2::error::{make_error_2007, reference_error}; +use crate::avm2::error::{make_error_1014, make_error_2007, Error1014Type}; use crate::avm2::object::TObject; use crate::avm2::parameters::ParametersExt; use crate::avm2::{Activation, Error, Object, Value}; @@ -125,13 +125,10 @@ pub fn get_class_by_alias<'gc>( if let Some(class_object) = activation.avm2().get_class_by_alias(name) { Ok(class_object.into()) } else { - // can't create error 1014 normally, - // as this is one place where it's a ReferenceError for some reason - let error = reference_error( + Err(make_error_1014( activation, - &format!("Error #1014: Class {} could not be found.", name), - 1014, - )?; - Err(Error::AvmError(error)) + Error1014Type::ReferenceError, + name, + )) } } diff --git a/core/src/avm2/verify.rs b/core/src/avm2/verify.rs index c492b4d668fa..81e8b963147f 100644 --- a/core/src/avm2/verify.rs +++ b/core/src/avm2/verify.rs @@ -1,7 +1,7 @@ use crate::avm2::class::Class; use crate::avm2::error::{ make_error_1014, make_error_1021, make_error_1025, make_error_1032, make_error_1054, - make_error_1107, verify_error, + make_error_1107, verify_error, Error1014Type, }; use crate::avm2::method::{BytecodeMethod, ParamConfig, ResolvedParamConfig}; use crate::avm2::multiname::Multiname; @@ -375,7 +375,11 @@ pub fn verify_method<'gc>( if multiname.has_lazy_component() { // This matches FP's error message - return Err(make_error_1014(activation, "[]".into())); + return Err(make_error_1014( + activation, + Error1014Type::VerifyError, + "[]".into(), + )); } activation @@ -384,7 +388,8 @@ pub fn verify_method<'gc>( .ok_or_else(|| { make_error_1014( activation, - multiname.to_qualified_name(activation.context.gc_context), + Error1014Type::VerifyError, + multiname.to_qualified_name(activation.gc()), ) })?; } @@ -424,7 +429,11 @@ pub fn verify_method<'gc>( if pooled_type_name.has_lazy_component() { // This matches FP's error message - return Err(make_error_1014(activation, "[]".into())); + return Err(make_error_1014( + activation, + Error1014Type::VerifyError, + "[]".into(), + )); } let resolved_type = activation @@ -433,6 +442,7 @@ pub fn verify_method<'gc>( .ok_or_else(|| { make_error_1014( activation, + Error1014Type::VerifyError, pooled_type_name.to_qualified_name(activation.context.gc_context), ) })?; @@ -675,7 +685,11 @@ pub fn resolve_param_config<'gc>( for param in param_config { let resolved_class = if let Some(param_type_name) = param.param_type_name { if param_type_name.has_lazy_component() { - return Err(make_error_1014(activation, "[]".into())); + return Err(make_error_1014( + activation, + Error1014Type::VerifyError, + "[]".into(), + )); } Some( @@ -685,6 +699,7 @@ pub fn resolve_param_config<'gc>( .ok_or_else(|| { make_error_1014( activation, + Error1014Type::VerifyError, param_type_name.to_qualified_name(activation.gc()), ) })?, @@ -709,7 +724,11 @@ fn resolve_return_type<'gc>( ) -> Result>, Error<'gc>> { if let Some(return_type) = return_type { if return_type.has_lazy_component() { - return Err(make_error_1014(activation, "[]".into())); + return Err(make_error_1014( + activation, + Error1014Type::VerifyError, + "[]".into(), + )); } Ok(Some( @@ -717,7 +736,11 @@ fn resolve_return_type<'gc>( .domain() .get_class(activation.context, &return_type) .ok_or_else(|| { - make_error_1014(activation, return_type.to_qualified_name(activation.gc())) + make_error_1014( + activation, + Error1014Type::VerifyError, + return_type.to_qualified_name(activation.gc()), + ) })?, )) } else { diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index dd390458da33..186d373b6740 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -1351,6 +1351,10 @@ impl<'gc> MovieClip<'gc> { / self.total_bytes() as f64) as u32 } + pub fn avm2_class(self) -> Option> { + *self.0.read().static_data.avm2_class.read() + } + pub fn set_avm2_class(self, gc_context: &Mutation<'gc>, constr: Option>) { *self.0.read().static_data.avm2_class.write(gc_context) = constr; } @@ -4525,26 +4529,18 @@ impl<'gc, 'a> MovieClip<'gc> { .write(context.gc_context) .remove(¤t_frame) { - let movie = self.movie(); let mut activation = Avm2Activation::from_nothing(context); + + let movie = self.movie(); + + let library = activation + .context + .library + .library_for_movie_mut(movie.clone()); + let domain = library.avm2_domain(); + for (name, id) in symbols { - let library = activation - .context - .library - .library_for_movie_mut(movie.clone()); - let domain = library.avm2_domain(); - let class_object = domain - .get_defined_value(&mut activation, name) - .and_then(|v| { - v.as_object() - .and_then(|o| o.as_class_object()) - .ok_or_else(|| { - format!("Attempted to assign a non-class {name:?} in SymbolClass",) - .into() - }) - }); - - match class_object { + match Avm2::lookup_class_for_character(&mut activation, self, domain, name, id) { Ok(class_object) => { activation .context @@ -4596,12 +4592,12 @@ impl<'gc, 'a> MovieClip<'gc> { *avm2_bitmapdata_class.write(activation.context.gc_context) = bitmap_class; } else { - tracing::error!("Associated class {:?} for symbol {} must extend flash.display.Bitmap or BitmapData, does neither", class_object.inner_class_definition().name(), self.id()); + tracing::error!("Associated class {:?} for symbol {} must extend flash.display.Bitmap or BitmapData, does neither", class_object.inner_class_definition().name(), id); } } None => { // Most SWFs use id 0 here, but some obfuscated SWFs can use other invalid IDs. - if self.0.read().static_data.avm2_class.read().is_none() { + if self.avm2_class().is_none() { self.set_avm2_class( activation.context.gc_context, Some(class_object), @@ -4616,7 +4612,7 @@ impl<'gc, 'a> MovieClip<'gc> { } } Err(e) => tracing::error!( - "Got AVM2 error {e:?} when attempting to assign symbol class {name:?}", + "Got AVM2 error when attempting to lookup symbol class: {e:?}", ), } } From bb43d1f80b7a6dedae77a423e24ad9dc2e03eba6 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 24 Nov 2024 21:56:59 -0800 Subject: [PATCH 4/6] avm2: Use direct slot access in native code for EventDispatcher This removes the `flash_events_internal` namespace from CommonNamespaces --- core/src/avm2/events.rs | 8 +++---- .../globals/flash/events/EventDispatcher.as | 10 ++++---- .../globals/flash/events/event_dispatcher.rs | 24 ++++++------------- core/src/avm2/namespace.rs | 2 -- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/core/src/avm2/events.rs b/core/src/avm2/events.rs index cb51961f8c64..5a5420256e8a 100644 --- a/core/src/avm2/events.rs +++ b/core/src/avm2/events.rs @@ -2,10 +2,10 @@ use crate::avm2::activation::Activation; use crate::avm2::error::make_error_2007; +use crate::avm2::globals::slots::*; use crate::avm2::object::{Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; -use crate::avm2::Multiname; use crate::display_object::TDisplayObject; use crate::string::AvmString; use fnv::FnvHashMap; @@ -383,9 +383,8 @@ fn dispatch_event_to_target<'gc>( event.as_event().unwrap().event_type(), ); - let internal_ns = activation.avm2().namespaces.flash_events_internal; let dispatch_list = dispatcher - .get_property(&Multiname::new(internal_ns, "_dispatchList"), activation)? + .get_slot(FLASH_EVENTS_EVENT_DISPATCHER__DISPATCH_LIST_SLOT) .as_object(); if dispatch_list.is_none() { @@ -447,9 +446,8 @@ pub fn dispatch_event<'gc>( event: Object<'gc>, simulate_dispatch: bool, ) -> Result> { - let internal_ns = activation.avm2().namespaces.flash_events_internal; let target = this - .get_property(&Multiname::new(internal_ns, "_target"), activation)? + .get_slot(FLASH_EVENTS_EVENT_DISPATCHER__TARGET_SLOT) .as_object() .unwrap_or(this); diff --git a/core/src/avm2/globals/flash/events/EventDispatcher.as b/core/src/avm2/globals/flash/events/EventDispatcher.as index 31f967c261c2..c5b127eb9d7f 100644 --- a/core/src/avm2/globals/flash/events/EventDispatcher.as +++ b/core/src/avm2/globals/flash/events/EventDispatcher.as @@ -1,11 +1,13 @@ -// This is a stub - the actual class is defined in `eventdispatcher.rs` package flash.events { public class EventDispatcher implements IEventDispatcher { - internal var _target:IEventDispatcher; - internal var _dispatchList:Object; + [Ruffle(InternalSlot)] + private var target:IEventDispatcher; + + [Ruffle(InternalSlot)] + private var dispatchList:Object; public function EventDispatcher(target:IEventDispatcher = null) { - this._target = target; + this.target = target; } public native function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void; diff --git a/core/src/avm2/globals/flash/events/event_dispatcher.rs b/core/src/avm2/globals/flash/events/event_dispatcher.rs index ec7ebf2d3b9b..bf2cbb156bfc 100644 --- a/core/src/avm2/globals/flash/events/event_dispatcher.rs +++ b/core/src/avm2/globals/flash/events/event_dispatcher.rs @@ -2,10 +2,10 @@ use crate::avm2::activation::Activation; use crate::avm2::events::{dispatch_event as dispatch_event_internal, parent_of}; +use crate::avm2::globals::slots::*; use crate::avm2::object::{DispatchObject, Object, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; -use crate::avm2::Multiname; use crate::avm2::{Avm2, Error}; /// Get an object's dispatch list, lazily initializing it if necessary. @@ -13,17 +13,12 @@ fn dispatch_list<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, ) -> Result, Error<'gc>> { - let namespaces = activation.avm2().namespaces; - - match this.get_property( - &Multiname::new(namespaces.flash_events_internal, "_dispatchList"), - activation, - )? { + match this.get_slot(FLASH_EVENTS_EVENT_DISPATCHER__DISPATCH_LIST_SLOT) { Value::Object(o) => Ok(o), _ => { let dispatch_list = DispatchObject::empty_list(activation); - this.init_property( - &Multiname::new(namespaces.flash_events_internal, "_dispatchList"), + this.set_slot( + FLASH_EVENTS_EVENT_DISPATCHER__DISPATCH_LIST_SLOT, dispatch_list.into(), activation, )?; @@ -86,7 +81,7 @@ pub fn has_event_listener<'gc>( let does_have = dispatch_list .as_dispatch_mut(activation.context.gc_context) - .ok_or_else(|| Error::from("Internal properties should have what I put in them"))? + .expect("Internal properties should have what I put in them") .has_event_listener(event_type) .into(); @@ -99,24 +94,19 @@ pub fn will_trigger<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let namespaces = activation.avm2().namespaces; - let dispatch_list = dispatch_list(activation, this)?; let event_type = args.get_string(activation, 0)?; if dispatch_list .as_dispatch_mut(activation.context.gc_context) - .ok_or_else(|| Error::from("Internal properties should have what I put in them"))? + .expect("Internal properties should have what I put in them") .has_event_listener(event_type) { return Ok(true.into()); } let target = this - .get_property( - &Multiname::new(namespaces.flash_events_internal, "_target"), - activation, - )? + .get_slot(FLASH_EVENTS_EVENT_DISPATCHER__TARGET_SLOT) .as_object() .unwrap_or(this); diff --git a/core/src/avm2/namespace.rs b/core/src/avm2/namespace.rs index 42e0b318681c..38578fc41f1c 100644 --- a/core/src/avm2/namespace.rs +++ b/core/src/avm2/namespace.rs @@ -345,7 +345,6 @@ pub struct CommonNamespaces<'gc> { // These are required to facilitate shared access between Rust and AS. pub(super) flash_utils_internal: Namespace<'gc>, - pub(super) flash_events_internal: Namespace<'gc>, pub(super) flash_text_engine_internal: Namespace<'gc>, pub(super) __ruffle__: Namespace<'gc>, @@ -373,7 +372,6 @@ impl<'gc> CommonNamespaces<'gc> { context, ), flash_utils_internal: Namespace::internal("flash.utils", context), - flash_events_internal: Namespace::internal("flash.events", context), flash_text_engine_internal: Namespace::internal("flash.text.engine", context), __ruffle__: Namespace::package("__ruffle__", ApiVersion::AllVersions, context), From e3947e51894f04c0776b843418ecac072cb41b6c Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Tue, 26 Nov 2024 12:25:24 -0800 Subject: [PATCH 5/6] tests: Add tests for SymbolClass linkage of EditText and Graphic --- .../tests/swfs/avm2/edit_text_linkage/output.txt | 8 ++++++++ tests/tests/swfs/avm2/edit_text_linkage/test.swf | Bin 0 -> 694 bytes .../tests/swfs/avm2/edit_text_linkage/test.toml | 1 + tests/tests/swfs/avm2/graphic_linkage/output.txt | 9 +++++++++ tests/tests/swfs/avm2/graphic_linkage/test.swf | Bin 0 -> 1183 bytes tests/tests/swfs/avm2/graphic_linkage/test.toml | 1 + 6 files changed, 19 insertions(+) create mode 100644 tests/tests/swfs/avm2/edit_text_linkage/output.txt create mode 100644 tests/tests/swfs/avm2/edit_text_linkage/test.swf create mode 100644 tests/tests/swfs/avm2/edit_text_linkage/test.toml create mode 100644 tests/tests/swfs/avm2/graphic_linkage/output.txt create mode 100644 tests/tests/swfs/avm2/graphic_linkage/test.swf create mode 100644 tests/tests/swfs/avm2/graphic_linkage/test.toml diff --git a/tests/tests/swfs/avm2/edit_text_linkage/output.txt b/tests/tests/swfs/avm2/edit_text_linkage/output.txt new file mode 100644 index 000000000000..2cace7d7ed47 --- /dev/null +++ b/tests/tests/swfs/avm2/edit_text_linkage/output.txt @@ -0,0 +1,8 @@ +MyText constructor +my text: +Testing +Main constructor +constructing child: +MyText constructor +my text: + diff --git a/tests/tests/swfs/avm2/edit_text_linkage/test.swf b/tests/tests/swfs/avm2/edit_text_linkage/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..0f9e5f6a2dd7f88ca94784ba9bc62da97216ddad GIT binary patch literal 694 zcmV;n0!jTtS5pf`1ONbdoPATvZqq;%y)*XM9y`wa(Yl4E5LzUv#1x4|HngOGic})e zF1o3(n+%On5=Y6jv@5DEk@y58K7l`A#Xeo|9qiblFq4r=1Bj75=gzrj&b=d#Pl3Dw zNPPlGAlS-S0MNjb$>dRDUW5pAl-`SaY*c>1-j-j+y|6E64Daa56yqO@+dnVP;X@h0 z1`^Z+)VK?<$3{HrAHjRWKtk&IUq7B_b66yI!l*BpowGf5%Hd1?v!*u58e8$^;|8Rm zDv8zQ4(EeH9ZMqtcDn8-Dk)`Fj z+=}}nK0I#mcxb3^L*?`|V%@f_?6_FFDpymG|DC8`sU4M*74BzMeBIp&+wDdt>b7%N zq_`;MmZa*4qci)6@u^^wqf=da6E1Gky45=?Pb(EIRK!G^(~3qhQNmu*OL=K8<7K^^ zr;$7<&6SGt-hx*mi#ozCMy5q=C*f*(GUbR>PQ>X*Hefcmu5f*q*E~g5gZ1Dbc8hI z=m-qJ2+`HC&mh2lK>fh-1Je%-KOlafi)SC*P@{vMiT@B|+qpOPzoRlF$J=mVuN9j% z60jt}dG5f)x68FVVeTMx*-XhTvVg6PeGzIRHkZc!L1LzuE1rE&MI0g1F|z2w*q8pv cwHwVO60kCbATK~jh%mC0>4rGzFM28sLex`P*8l(j literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/edit_text_linkage/test.toml b/tests/tests/swfs/avm2/edit_text_linkage/test.toml new file mode 100644 index 000000000000..dbee897f5863 --- /dev/null +++ b/tests/tests/swfs/avm2/edit_text_linkage/test.toml @@ -0,0 +1 @@ +num_frames = 1 diff --git a/tests/tests/swfs/avm2/graphic_linkage/output.txt b/tests/tests/swfs/avm2/graphic_linkage/output.txt new file mode 100644 index 000000000000..0d4282161d25 --- /dev/null +++ b/tests/tests/swfs/avm2/graphic_linkage/output.txt @@ -0,0 +1,9 @@ +MyText constructor +my shape: +40.35 +Main constructor +constructing child: +MyText constructor +my shape: +0 +0 diff --git a/tests/tests/swfs/avm2/graphic_linkage/test.swf b/tests/tests/swfs/avm2/graphic_linkage/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..ceabd7ac9f2f8f3cdfeb67e31adca8731c2acbed GIT binary patch literal 1183 zcmV;Q1Yr9^S5pi21pokeoPAVlOdM4dzIS#WJG;Eu2fMs01r)+gp(VOMY+<2V3TzY_ zDRpIE13Pvfurn-RqBK5Q>jQ|GY7~LA6;gvH)mCk*f(6qc7Pdja6ltTWVzp>eK?^?C zJ4`}LHQwC0=R4my_ndR*-kfGY)Bq6K3qTkIMbUBqfC&qQLR-Uh2tt6vs7lVm@+swP zQ_6!rkIjo{3=D#U5C9FRyfkJi{{cZXqSU4C+0TccefK$MU;4zNBWr7C&*+ShwA^ee z{bsE9`+)BHwc`GC{%Uo=^vJH5>T@-t?bmm|(_MDDw0b3Nyz!^P&XR@u-`nu|(Q{kc zcXjQYvM`{hlkDl3xMKe(RNI=TW*0{;7B}6qCzX9Gq3y*R%YP5NGI^#d;sMKM>X@VO z{HCT4R%Xo8bqkBWm)bt-&&Ypf?rMZbx44wlVlHjiJIt4HP92%QA(6F@cyT#dzKl(x^S=Z%( zjSqK?4p^Q)KKJsTq}_bp&!_%!FSk|xdGunarpvq!uJHS-&we@{zgJz8f9k{Y?|V)> zB@CT(s;fsUUj3vY?@Z>U)}85ZLU-F-rss{1N2)H5w_iyQ9LTzMY-qou%P{e~{q5~v z-CNF)Uq3zFtFdoB9npGZW9~zSzHevM9ydIDFLg-6N5ts zNs>mlp;<}ZHmC~8R<8qyH&)~;C^Ug6kRuY=sV+gNHyR8!r_avjID8&MK}DV+mtpb^ z_D0Sva9)6y**Gt0wm-@`1iH)@;8>HJs}HYr+jv)wljG~%widFY-p>iFrbtpHXOqt> zpslPwdWTnF{Wgcd1z5=xzRoeSIKX-Zk+KO67wZp`VzVO4(_(sqxMT)C$uWi=B~;j-1UvA6wMk(1Z?ZS^kB!PCf>7a1eb#5o0* zRt_~7A&Zk~NKKd)(?{qd^-=n0eT+UErcUB0--?Kr9@Cl$=&5!@^~F zM5GdFu?R;+tJDgFH4@ZQn2f@3S_UbCpa_a0Kx@Ev`JoP;0~+yN}w>_oE`B5~KM2-e0S ziG0K;lq|>~C_|Xe2r?|V8IfYFBx5BQD~@IrOq7O8+=*kDLYWg}et{_S>1mLeKyJ literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/graphic_linkage/test.toml b/tests/tests/swfs/avm2/graphic_linkage/test.toml new file mode 100644 index 000000000000..dbee897f5863 --- /dev/null +++ b/tests/tests/swfs/avm2/graphic_linkage/test.toml @@ -0,0 +1 @@ +num_frames = 1 From 5a6880dd0de5459bc8228b1f1e167c043a090964 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Wed, 27 Nov 2024 20:57:18 -0800 Subject: [PATCH 6/6] tests: Fix `avm2/bytearray_compress` test --- .../tests/swfs/avm2/bytearray_compress/Test.as | 3 ++- .../tests/swfs/avm2/bytearray_compress/test.swf | Bin 969 -> 992 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/tests/swfs/avm2/bytearray_compress/Test.as b/tests/tests/swfs/avm2/bytearray_compress/Test.as index 066f9c47192d..ba478c7997ca 100644 --- a/tests/tests/swfs/avm2/bytearray_compress/Test.as +++ b/tests/tests/swfs/avm2/bytearray_compress/Test.as @@ -1,11 +1,12 @@ // compiled with mxmlc package { + import flash.display.MovieClip; import flash.utils.ByteArray; import flash.utils.Endian; import flash.utils.CompressionAlgorithm; - public class Test { + public class Test extends MovieClip { public function Test() { var ba = createByteArray(); diff --git a/tests/tests/swfs/avm2/bytearray_compress/test.swf b/tests/tests/swfs/avm2/bytearray_compress/test.swf index 2cb295c70a3fbf800983c608bdaa6cd0d791954f..26a80c58b792d9e5131f6b60247476b97f102d92 100644 GIT binary patch delta 717 zcmV;;0y6!{2jB+@T31st{sjO4&#?(20e|OLv=>>%g>>?X{BJ3Zrx5TvcKw0$#+p-X z$tj4XP7ZF+kXAC8y)GjMZ#CyuUOU{|S|*9ILO~b@Gi3BJDH$Wj?fa+_)0cg_59Xw` zI0UE?3_{FT{KJn~kGE|Tpy?mM%-WWABN79Kq&d0!7vC*#x+BwQhLb&PtOMjkhkwjc~OYLQ#@9q<+;OGs9TOmFz}&R_a#>Qjnm ziImr&@z2?D(YkgH?0S4an$(?V@_+g;05Y57tzICGjf&F{O`>!qp7q8c>PF%J9_O=T zl24LUuil{Tm&)h$E$dDF$|!Ed7eLTca00*Y6}rcbM6kqXk?8vx$Up89Z1QX{p9Q_= ze}^C_wOP-qNDSkolls z!tXCYv?IvA+`@YDz~+G4>!ZHmYmPHWt`tY<5rzY2995f z<;Vx0^SH^u-Gnh$zrH}|5=M<8Y^CpY2DttzYuI3nDQ-E+2 zu)qb*{BO=bOu1;9b3lw%h`5d2j%d-)Z#%$QH1WQr0lLv}uys_J8XcR=WlFq3qJM>f zy*z~pa0`hFpXxGdbI=hVT*S+-B4Itj+6fHjdEPawOvR(Azi8QsQ_Z79Lz5u061 zV>|8=77iQP_+253?e9;Rr;ePYet(t;Gs~W0j)6SbZn;}iC0jwGTZ}ERX{qHd@-Qs) z6^nf1i+r}NQx*t)SB3rJf3#4@>_baX49a?X1|@6Oh+0|7XBPDU82c_WVb3H-i=*0Z z0ih_JU4~Rm1bO|Yg%5uPSenh;Y->s}GN=OFO7&k`!iZa)Ao)7>jRJDCmVabPyGSFg zj}?{$ulx)2mTa1xL|ePSlKA`pVJQoTr^b{iR|Q9*Jo$0|E1mIOa)UTT~t&f`Tn=zX zu@DFRQP|ST$M;M@wp6XPbNW