diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index a7c8c6644270..f7669e43fdf6 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -218,6 +218,19 @@ impl<'a, 'gc> Activation<'a, 'gc> { &mut self.context.strings } + pub fn prototypes(&self) -> &crate::avm1::globals::SystemPrototypes<'gc> { + self.context.avm1.prototypes(self.swf_version()) + } + + /// Obtain a reference to the global scope. + pub fn global_scope(&self) -> Gc<'gc, Scope<'gc>> { + self.context.avm1.global_scope(self.swf_version()) + } + /// Obtain a reference to `_global`. + pub fn global_object(&self) -> Object<'gc> { + self.global_scope().locals_cell() + } + #[expect(clippy::too_many_arguments)] pub fn from_action( context: &'a mut UpdateContext<'gc>, @@ -282,15 +295,17 @@ impl<'a, 'gc> Activation<'a, 'gc> { ) -> Self { avm_debug!(context.avm1, "START {id}"); + let swf_version = base_clip.swf_version(); + let scope = context.avm1.global_scope(swf_version); Self { id, - swf_version: base_clip.swf_version(), - scope: context.avm1.global_scope(), + swf_version, + scope, constant_pool: context.avm1.constant_pool(), base_clip, target_clip: Some(base_clip), base_clip_unloaded: base_clip.avm1_removed(), - this: context.avm1.global_object().into(), + this: scope.locals_cell().into(), callee: None, local_registers: None, context, @@ -380,7 +395,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let child_scope = Gc::new( self.gc(), Scope::new( - self.context.avm1.global_scope(), + self.context.avm1.global_scope(swf_version), scope::ScopeClass::Target, clip_obj, ), @@ -882,14 +897,11 @@ impl<'a, 'gc> Activation<'a, 'gc> { MovieClipReference::try_from_stage_object(self, bc).unwrap(), ); let name = func.name(); - let prototype = Object::new( - &self.context.strings, - Some(self.context.avm1.prototypes().object), - ); + let prototype = Object::new(&self.context.strings, Some(self.prototypes().object)); let func_obj = FunctionObject::function( &self.context.strings, Gc::new(self.gc(), func), - self.context.avm1.prototypes().function, + self.prototypes().function, prototype, ); if let Some(name) = name { @@ -1453,10 +1465,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { // InitArray pops no args and pushes undefined if num_props is out of range. Value::Undefined } else { - let object = Object::new( - &self.context.strings, - Some(self.context.avm1.prototypes().object), - ); + let object = Object::new(&self.context.strings, Some(self.prototypes().object)); for _ in 0..num_props as usize { let value = self.context.avm1.pop(); let name_val = self.context.avm1.pop(); @@ -2932,7 +2941,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { /// Returns whether property keys should be case sensitive based on the current SWF version. pub fn is_case_sensitive(&self) -> bool { - self.swf_version() > 6 + crate::avm1::runtime::Avm1::is_case_sensitive(self.swf_version()) } /// Resolve a particular named local variable within this activation. diff --git a/core/src/avm1/flv.rs b/core/src/avm1/flv.rs index b4ea6627198e..b104ec44654a 100644 --- a/core/src/avm1/flv.rs +++ b/core/src/avm1/flv.rs @@ -6,7 +6,7 @@ fn avm1_object_from_flv_variables<'gc>( activation: &mut Activation<'_, 'gc>, variables: Vec, ) -> Avm1Value<'gc> { - let object_proto = activation.context.avm1.prototypes().object; + let object_proto = activation.prototypes().object; let info_object = Object::new(activation.strings(), Some(object_proto)); for value in variables { @@ -29,7 +29,7 @@ fn avm1_date_from_flv_date<'gc>( unix_time: f64, _local_offset: i16, ) -> Avm1Value<'gc> { - let constructor = activation.context.avm1.prototypes().date_constructor; + let constructor = activation.prototypes().date_constructor; let result = constructor.construct(activation, &[unix_time.into()]); result.expect("AVM1 date constructed from FLV date") diff --git a/core/src/avm1/function.rs b/core/src/avm1/function.rs index a74711437c1b..2ebd3a19dfad 100644 --- a/core/src/avm1/function.rs +++ b/core/src/avm1/function.rs @@ -254,7 +254,7 @@ impl<'gc> Avm1Function<'gc> { fn load_global(&self, frame: &mut Activation<'_, 'gc>, preload_r: &mut u8) { if self.flags.contains(FunctionFlags::PRELOAD_GLOBAL) { - let global = frame.context.avm1.global_object(); + let global = frame.global_object(); frame.set_local_register(*preload_r, global.into()); *preload_r += 1; } @@ -394,7 +394,7 @@ impl<'gc> Executable<'gc> { let scope = Gc::new( activation.gc(), Scope::new( - activation.context.avm1.global_scope(), + activation.global_scope(), super::scope::ScopeClass::Target, base_clip_obj, ), diff --git a/core/src/avm1/globals/array.rs b/core/src/avm1/globals/array.rs index 86a9022ead91..762b6fb17f9b 100644 --- a/core/src/avm1/globals/array.rs +++ b/core/src/avm1/globals/array.rs @@ -92,7 +92,7 @@ impl<'gc> ArrayBuilder<'gc> { } pub fn new(activation: &Activation<'_, 'gc>) -> Self { - let proto = activation.context.avm1.prototypes().array; + let proto = activation.prototypes().array; Self::new_with_proto(&activation.context.strings, proto) } diff --git a/core/src/avm1/globals/as_broadcaster.rs b/core/src/avm1/globals/as_broadcaster.rs index a745ab834ad9..cd3a8d5000b0 100644 --- a/core/src/avm1/globals/as_broadcaster.rs +++ b/core/src/avm1/globals/as_broadcaster.rs @@ -169,8 +169,11 @@ fn initialize<'gc>( initialize_internal( &activation.context.strings, broadcaster, - activation.context.avm1.broadcaster_functions(), - activation.context.avm1.prototypes().array, + activation + .context + .avm1 + .broadcaster_functions(activation.swf_version()), + activation.prototypes().array, ); } Ok(Value::Undefined) diff --git a/core/src/avm1/globals/bitmap_data.rs b/core/src/avm1/globals/bitmap_data.rs index 1a7782b63aeb..597e150355d1 100644 --- a/core/src/avm1/globals/bitmap_data.rs +++ b/core/src/avm1/globals/bitmap_data.rs @@ -173,7 +173,7 @@ fn get_rectangle<'gc>( ) -> Result, Error<'gc>> { if let NativeObject::BitmapData(bitmap_data) = this.native() { if !bitmap_data.disposed() { - let proto = activation.context.avm1.prototypes().rectangle_constructor; + let proto = activation.prototypes().rectangle_constructor; let rect = proto.construct( activation, &[ @@ -772,7 +772,7 @@ fn get_color_bounds_rect<'gc>( color, ); - let proto = activation.context.avm1.prototypes().rectangle_constructor; + let proto = activation.prototypes().rectangle_constructor; let rect = proto.construct(activation, &[x.into(), y.into(), w.into(), h.into()])?; return Ok(rect); diff --git a/core/src/avm1/globals/bitmap_filter.rs b/core/src/avm1/globals/bitmap_filter.rs index 8f2a5f8fe37c..c43b673cbf8c 100644 --- a/core/src/avm1/globals/bitmap_filter.rs +++ b/core/src/avm1/globals/bitmap_filter.rs @@ -100,48 +100,48 @@ pub fn filter_to_avm1<'gc>(activation: &mut Activation<'_, 'gc>, filter: Filter) let (native, proto) = match filter { Filter::BevelFilter(filter) => ( NativeObject::BevelFilter(BevelFilter::from_filter(activation.gc(), filter)), - activation.context.avm1.prototypes().bevel_filter, + activation.prototypes().bevel_filter, ), Filter::BlurFilter(filter) => ( NativeObject::BlurFilter(BlurFilter::from_filter(activation.gc(), filter)), - activation.context.avm1.prototypes().blur_filter, + activation.prototypes().blur_filter, ), Filter::ColorMatrixFilter(filter) => ( NativeObject::ColorMatrixFilter(ColorMatrixFilter::from_filter( activation.gc(), filter, )), - activation.context.avm1.prototypes().color_matrix_filter, + activation.prototypes().color_matrix_filter, ), Filter::ConvolutionFilter(filter) => ( NativeObject::ConvolutionFilter(ConvolutionFilter::from_filter( activation.gc(), filter, )), - activation.context.avm1.prototypes().convolution_filter, + activation.prototypes().convolution_filter, ), Filter::GlowFilter(filter) => ( NativeObject::GlowFilter(GlowFilter::from_filter(activation.gc(), filter)), - activation.context.avm1.prototypes().glow_filter, + activation.prototypes().glow_filter, ), Filter::DropShadowFilter(filter) => ( NativeObject::DropShadowFilter(DropShadowFilter::from_filter(activation.gc(), filter)), - activation.context.avm1.prototypes().drop_shadow_filter, + activation.prototypes().drop_shadow_filter, ), Filter::DisplacementMapFilter(filter) => ( NativeObject::DisplacementMapFilter(DisplacementMapFilter::from_filter( activation.gc(), filter, )), - activation.context.avm1.prototypes().displacement_map_filter, + activation.prototypes().displacement_map_filter, ), Filter::GradientBevelFilter(filter) => ( NativeObject::GradientBevelFilter(GradientFilter::from_filter(activation.gc(), filter)), - activation.context.avm1.prototypes().gradient_bevel_filter, + activation.prototypes().gradient_bevel_filter, ), Filter::GradientGlowFilter(filter) => ( NativeObject::GradientGlowFilter(GradientFilter::from_filter(activation.gc(), filter)), - activation.context.avm1.prototypes().gradient_glow_filter, + activation.prototypes().gradient_glow_filter, ), Filter::ShaderFilter(_) => { unreachable!( diff --git a/core/src/avm1/globals/color.rs b/core/src/avm1/globals/color.rs index d5d08533ffb9..3959b092bf43 100644 --- a/core/src/avm1/globals/color.rs +++ b/core/src/avm1/globals/color.rs @@ -92,7 +92,7 @@ fn get_transform<'gc>( let color_transform = base.color_transform(); let out = Object::new( &activation.context.strings, - Some(activation.context.avm1.prototypes().object), + Some(activation.prototypes().object), ); out.set( istr!("ra"), diff --git a/core/src/avm1/globals/color_transform.rs b/core/src/avm1/globals/color_transform.rs index c8e4ce1a3231..a5a652aa34ee 100644 --- a/core/src/avm1/globals/color_transform.rs +++ b/core/src/avm1/globals/color_transform.rs @@ -50,11 +50,7 @@ impl<'gc> ColorTransformObject { color_transform.b_add.into(), color_transform.a_add.into(), ]; - let constructor = activation - .context - .avm1 - .prototypes() - .color_transform_constructor; + let constructor = activation.prototypes().color_transform_constructor; constructor.construct(activation, &args) } diff --git a/core/src/avm1/globals/context_menu.rs b/core/src/avm1/globals/context_menu.rs index aceb8c829b57..20e9d27628c1 100644 --- a/core/src/avm1/globals/context_menu.rs +++ b/core/src/avm1/globals/context_menu.rs @@ -34,7 +34,7 @@ fn constructor<'gc>( let built_in_items = Object::new( &activation.context.strings, - Some(activation.context.avm1.prototypes().object), + Some(activation.prototypes().object), ); built_in_items.set(istr!("print"), true.into(), activation)?; @@ -48,7 +48,7 @@ fn constructor<'gc>( this.set(istr!("builtInItems"), built_in_items.into(), activation)?; - let constructor = activation.context.avm1.prototypes().array_constructor; + let constructor = activation.prototypes().array_constructor; let custom_items = constructor.construct(activation, &[])?; this.set(istr!("customItems"), custom_items, activation)?; @@ -65,11 +65,7 @@ pub fn copy<'gc>( .get(istr!("onSelect"), activation)? .coerce_to_object(activation); - let constructor = activation - .context - .avm1 - .prototypes() - .context_menu_constructor; + let constructor = activation.prototypes().context_menu_constructor; let copy = constructor .construct(activation, &[callback.into()])? .coerce_to_object(activation); diff --git a/core/src/avm1/globals/context_menu_item.rs b/core/src/avm1/globals/context_menu_item.rs index 7b4300084727..cd23f8f15815 100644 --- a/core/src/avm1/globals/context_menu_item.rs +++ b/core/src/avm1/globals/context_menu_item.rs @@ -81,11 +81,7 @@ pub fn copy<'gc>( .get(istr!("visible"), activation)? .as_bool(activation.swf_version()); - let constructor = activation - .context - .avm1 - .prototypes() - .context_menu_item_constructor; + let constructor = activation.prototypes().context_menu_item_constructor; let copy = constructor.construct( activation, &[ diff --git a/core/src/avm1/globals/displacement_map_filter.rs b/core/src/avm1/globals/displacement_map_filter.rs index d93f2fe7025e..7e640a6612a0 100644 --- a/core/src/avm1/globals/displacement_map_filter.rs +++ b/core/src/avm1/globals/displacement_map_filter.rs @@ -76,11 +76,11 @@ impl<'gc> DisplacementMapFilter<'gc> { Self(Gc::new(gc_context, self.0.as_ref().clone())) } - fn map_bitmap(self, context: &mut UpdateContext<'gc>) -> Option> { + fn map_bitmap(self, activation: &mut Activation<'_, 'gc>) -> Option> { if let Some(map_bitmap) = self.0.map_bitmap.get() { - let proto = context.avm1.prototypes().bitmap_data; - let result = Object::new(&context.strings, Some(proto)); - result.set_native(context.gc(), NativeObject::BitmapData(map_bitmap)); + let proto = activation.prototypes().bitmap_data; + let result = Object::new(activation.strings(), Some(proto)); + result.set_native(activation.gc(), NativeObject::BitmapData(map_bitmap)); Some(result) } else { None @@ -108,7 +108,7 @@ impl<'gc> DisplacementMapFilter<'gc> { fn map_point(self, activation: &mut Activation<'_, 'gc>) -> Result, Error<'gc>> { let map_point = self.0.map_point.get(); let args = &[map_point.x.into(), map_point.y.into()]; - let constructor = activation.context.avm1.prototypes().point_constructor; + let constructor = activation.prototypes().point_constructor; constructor.construct(activation, args) } @@ -353,7 +353,7 @@ fn method<'gc>( Ok(match index { GET_MAP_BITMAP => this - .map_bitmap(activation.context) + .map_bitmap(activation) .map_or(Value::Undefined, Value::from), SET_MAP_BITMAP => { this.set_map_bitmap(activation, args.get(0))?; diff --git a/core/src/avm1/globals/file_reference.rs b/core/src/avm1/globals/file_reference.rs index 593c7d99fadf..88859c631c55 100644 --- a/core/src/avm1/globals/file_reference.rs +++ b/core/src/avm1/globals/file_reference.rs @@ -37,7 +37,7 @@ impl<'gc> FileReferenceObject<'gc> { self.0.is_initialised.set(true); - let date_proto = activation.context.avm1.prototypes().date_constructor; + let date_proto = activation.prototypes().date_constructor; if let Some(creation_time) = result.creation_time() { if let Ok(Value::Object(obj)) = date_proto.construct( activation, diff --git a/core/src/avm1/globals/function.rs b/core/src/avm1/globals/function.rs index 46be8038e794..810511fcf11e 100644 --- a/core/src/avm1/globals/function.rs +++ b/core/src/avm1/globals/function.rs @@ -51,7 +51,7 @@ pub fn call<'gc>( myargs: &[Value<'gc>], ) -> Result, Error<'gc>> { let this = match myargs.get(0).unwrap_or(&Value::Undefined) { - Value::Undefined | Value::Null => activation.context.avm1.global_object(), + Value::Undefined | Value::Null => activation.global_object(), this_val => this_val.coerce_to_object(activation), }; let empty = []; @@ -83,7 +83,7 @@ pub fn apply<'gc>( myargs: &[Value<'gc>], ) -> Result, Error<'gc>> { let this = match myargs.get(0).unwrap_or(&Value::Undefined) { - Value::Undefined | Value::Null => activation.context.avm1.global_object(), + Value::Undefined | Value::Null => activation.global_object(), this_val => this_val.coerce_to_object(activation), }; let args_object = myargs.get(1).cloned().unwrap_or(Value::Undefined); diff --git a/core/src/avm1/globals/local_connection.rs b/core/src/avm1/globals/local_connection.rs index 14e80a7a5dfb..419bb1a1df14 100644 --- a/core/src/avm1/globals/local_connection.rs +++ b/core/src/avm1/globals/local_connection.rs @@ -78,7 +78,7 @@ impl<'gc> LocalConnection<'gc> { ActivationIdentifier::root("[LocalConnection onStatus]"), root_clip, ); - let constructor = activation.context.avm1.prototypes().object_constructor; + let constructor = activation.prototypes().object_constructor; let event = constructor .construct(&mut activation, &[])? .coerce_to_object(&mut activation); diff --git a/core/src/avm1/globals/math.rs b/core/src/avm1/globals/math.rs index 84dda32acd0f..27e139b38920 100644 --- a/core/src/avm1/globals/math.rs +++ b/core/src/avm1/globals/math.rs @@ -172,8 +172,8 @@ mod tests { fn setup<'gc>(activation: &mut Activation<'_, 'gc>) -> Object<'gc> { create(&mut DeclContext { - object_proto: activation.context.avm1.prototypes().object, - fn_proto: activation.context.avm1.prototypes().function, + object_proto: activation.prototypes().object, + fn_proto: activation.prototypes().function, strings: activation.strings(), }) } diff --git a/core/src/avm1/globals/matrix.rs b/core/src/avm1/globals/matrix.rs index 05fb7e753073..5397fbd77d64 100644 --- a/core/src/avm1/globals/matrix.rs +++ b/core/src/avm1/globals/matrix.rs @@ -179,7 +179,7 @@ pub fn matrix_to_value<'gc>( matrix.tx.to_pixels().into(), matrix.ty.to_pixels().into(), ]; - let constructor = activation.context.avm1.prototypes().matrix_constructor; + let constructor = activation.prototypes().matrix_constructor; let object = constructor.construct(activation, &args)?; Ok(object) } @@ -251,7 +251,7 @@ fn clone<'gc>( this.get(istr!("tx"), activation)?, this.get(istr!("ty"), activation)?, ]; - let constructor = activation.context.avm1.prototypes().matrix_constructor; + let constructor = activation.prototypes().matrix_constructor; let cloned = constructor.construct(activation, &args)?; Ok(cloned) } diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index b78aeedd0d53..6023d46596a9 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -141,7 +141,7 @@ pub fn new_rectangle<'gc>( let width = rectangle.width().to_pixels(); let height = rectangle.height().to_pixels(); let args = &[x.into(), y.into(), width.into(), height.into()]; - let proto = activation.context.avm1.prototypes().rectangle_constructor; + let proto = activation.prototypes().rectangle_constructor; proto.construct(activation, args) } @@ -1459,7 +1459,7 @@ fn get_bounds<'gc>( let out = Object::new( &activation.context.strings, - Some(activation.context.avm1.prototypes().object), + Some(activation.prototypes().object), ); out.set( istr!("xMin"), @@ -1642,7 +1642,7 @@ fn transform<'gc>( this: MovieClip<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let constructor = activation.context.avm1.prototypes().transform_constructor; + let constructor = activation.prototypes().transform_constructor; let cloned = constructor.construct(activation, &[this.object()])?; Ok(cloned) } diff --git a/core/src/avm1/globals/netconnection.rs b/core/src/avm1/globals/netconnection.rs index 3dbcef223246..b0e9a7063f3c 100644 --- a/core/src/avm1/globals/netconnection.rs +++ b/core/src/avm1/globals/netconnection.rs @@ -59,7 +59,7 @@ impl<'gc> NetConnection<'gc> { ActivationIdentifier::root("[NetConnection connect]"), root_clip, ); - let constructor = activation.context.avm1.prototypes().object_constructor; + let constructor = activation.prototypes().object_constructor; let event = constructor .construct(&mut activation, &[])? .coerce_to_object(&mut activation); diff --git a/core/src/avm1/globals/point.rs b/core/src/avm1/globals/point.rs index adade884a189..0ffc869f5d92 100644 --- a/core/src/avm1/globals/point.rs +++ b/core/src/avm1/globals/point.rs @@ -48,7 +48,7 @@ pub fn construct_new_point<'gc>( args: &[Value<'gc>], activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let constructor = activation.context.avm1.prototypes().point_constructor; + let constructor = activation.prototypes().point_constructor; let object = constructor.construct(activation, args)?; Ok(object) } @@ -114,7 +114,7 @@ fn clone<'gc>( this.get(istr!("x"), activation)?, this.get(istr!("y"), activation)?, ]; - let constructor = activation.context.avm1.prototypes().point_constructor; + let constructor = activation.prototypes().point_constructor; let cloned = constructor.construct(activation, &args)?; Ok(cloned) diff --git a/core/src/avm1/globals/rectangle.rs b/core/src/avm1/globals/rectangle.rs index 931b90f3b751..df954c392720 100644 --- a/core/src/avm1/globals/rectangle.rs +++ b/core/src/avm1/globals/rectangle.rs @@ -138,7 +138,7 @@ fn clone<'gc>( this.get(istr!("width"), activation)?, this.get(istr!("height"), activation)?, ]; - let constructor = activation.context.avm1.prototypes().rectangle_constructor; + let constructor = activation.prototypes().rectangle_constructor; let cloned = constructor.construct(activation, &args)?; Ok(cloned) } @@ -384,7 +384,7 @@ fn union<'gc>( this_bottom.max(other_bottom) } - top; - let constructor = activation.context.avm1.prototypes().rectangle_constructor; + let constructor = activation.prototypes().rectangle_constructor; let result = constructor.construct( activation, &[left.into(), top.into(), width.into(), height.into()], @@ -592,7 +592,7 @@ fn intersection<'gc>( top = 0.0; } - let constructor = activation.context.avm1.prototypes().rectangle_constructor; + let constructor = activation.prototypes().rectangle_constructor; let result = constructor.construct( activation, &[ @@ -619,8 +619,8 @@ fn equals<'gc>( let other_y = other.get(istr!("y"), activation)?; let other_width = other.get(istr!("width"), activation)?; let other_height = other.get(istr!("height"), activation)?; - let proto = activation.context.avm1.prototypes().rectangle; - let constructor = activation.context.avm1.prototypes().rectangle_constructor; + let proto = activation.prototypes().rectangle; + let constructor = activation.prototypes().rectangle_constructor; return Ok((this_x == other_x && this_y == other_y && this_width == other_width diff --git a/core/src/avm1/globals/shared_object.rs b/core/src/avm1/globals/shared_object.rs index 63640acf2e06..730f6d8517fe 100644 --- a/core/src/avm1/globals/shared_object.rs +++ b/core/src/avm1/globals/shared_object.rs @@ -165,7 +165,7 @@ pub fn deserialize_value<'gc>( AmfValue::String(s) => Value::String(AvmString::new_utf8(activation.gc(), s)), AmfValue::Bool(b) => (*b).into(), AmfValue::ECMAArray(_, _, associative, len) => { - let array_constructor = activation.context.avm1.prototypes().array_constructor; + let array_constructor = activation.prototypes().array_constructor; if let Ok(Value::Object(obj)) = array_constructor.construct(activation, &[(*len).into()]) { @@ -200,7 +200,7 @@ pub fn deserialize_value<'gc>( // Deserialize Object let obj = Object::new( &activation.context.strings, - Some(activation.context.avm1.prototypes().object), + Some(activation.prototypes().object), ); let v: Value<'gc> = obj.into(); @@ -219,7 +219,7 @@ pub fn deserialize_value<'gc>( v } AmfValue::Date(time, _) => { - let date_proto = activation.context.avm1.prototypes().date_constructor; + let date_proto = activation.prototypes().date_constructor; if let Ok(Value::Object(obj)) = date_proto.construct(activation, &[(*time).into()]) { Value::Object(obj) @@ -228,7 +228,7 @@ pub fn deserialize_value<'gc>( } } AmfValue::XML(content, _) => { - let xml_proto = activation.context.avm1.prototypes().xml_constructor; + let xml_proto = activation.prototypes().xml_constructor; if let Ok(Value::Object(obj)) = xml_proto.construct( activation, @@ -257,7 +257,7 @@ fn deserialize_lso<'gc>( ) -> Result, Error<'gc>> { let obj = Object::new( &activation.context.strings, - Some(activation.context.avm1.prototypes().object), + Some(activation.prototypes().object), ); let mut reference_cache = BTreeMap::default(); @@ -406,11 +406,7 @@ fn get_local<'gc>( } // Data property only should exist when created with getLocal/Remote - let constructor = activation - .context - .avm1 - .prototypes() - .shared_object_constructor; + let constructor = activation.prototypes().shared_object_constructor; let this = constructor .construct(activation, &[])? .coerce_to_object(activation); @@ -434,7 +430,7 @@ fn get_local<'gc>( // No data; create a fresh data object. data = Object::new( &activation.context.strings, - Some(activation.context.avm1.prototypes().object), + Some(activation.prototypes().object), ) .into(); } diff --git a/core/src/avm1/globals/sound.rs b/core/src/avm1/globals/sound.rs index a1602bc8a5c2..842440c68067 100644 --- a/core/src/avm1/globals/sound.rs +++ b/core/src/avm1/globals/sound.rs @@ -370,7 +370,7 @@ fn get_transform<'gc>( let obj = Object::new( &activation.context.strings, - Some(activation.context.avm1.prototypes().object), + Some(activation.prototypes().object), ); // Surprisingly `lr` means "right-to-left" and `rl` means "left-to-right". obj.set(istr!("ll"), transform.left_to_left.into(), activation)?; diff --git a/core/src/avm1/globals/style_sheet.rs b/core/src/avm1/globals/style_sheet.rs index 64ee64a95643..c84e360f09c8 100644 --- a/core/src/avm1/globals/style_sheet.rs +++ b/core/src/avm1/globals/style_sheet.rs @@ -73,7 +73,7 @@ fn shallow_copy<'gc>( value: Value<'gc>, ) -> Result, Error<'gc>> { if let Value::Object(object) = value { - let object_proto = activation.context.avm1.prototypes().object; + let object_proto = activation.prototypes().object; let result = Object::new(activation.strings(), Some(object_proto)); for key in object.get_keys(activation, false) { @@ -377,7 +377,7 @@ fn transform<'gc>( } } - let proto = activation.context.avm1.prototypes().text_format; + let proto = activation.prototypes().text_format; let object = Object::new(activation.strings(), Some(proto)); object.set_native( activation.gc(), @@ -399,7 +399,7 @@ fn parse_css<'gc>( if let Ok(css) = CssStream::new(&source).parse() { for (selector, properties) in css.into_iter() { if !selector.is_empty() { - let proto = activation.context.avm1.prototypes().object; + let proto = activation.prototypes().object; let object = Object::new(activation.strings(), Some(proto)); for (key, value) in properties.into_iter() { diff --git a/core/src/avm1/globals/text_field.rs b/core/src/avm1/globals/text_field.rs index 59f16b1f85fa..5ba5feeff374 100644 --- a/core/src/avm1/globals/text_field.rs +++ b/core/src/avm1/globals/text_field.rs @@ -129,7 +129,7 @@ fn new_text_format<'gc>( activation: &mut Activation<'_, 'gc>, text_format: TextFormat, ) -> Object<'gc> { - let proto = activation.context.avm1.prototypes().text_format; + let proto = activation.prototypes().text_format; let object = Object::new(&activation.context.strings, Some(proto)); object.set_native( activation.gc(), diff --git a/core/src/avm1/globals/transform.rs b/core/src/avm1/globals/transform.rs index da123d452da8..28a3db47ac26 100644 --- a/core/src/avm1/globals/transform.rs +++ b/core/src/avm1/globals/transform.rs @@ -168,7 +168,7 @@ fn method<'gc>( }; // Return Rectangle object. - let constructor = activation.context.avm1.prototypes().rectangle_constructor; + let constructor = activation.prototypes().rectangle_constructor; constructor.construct( activation, &[ diff --git a/core/src/avm1/globals/xml_socket.rs b/core/src/avm1/globals/xml_socket.rs index d3ed2ad1c6cd..ea4a357c2df0 100644 --- a/core/src/avm1/globals/xml_socket.rs +++ b/core/src/avm1/globals/xml_socket.rs @@ -208,7 +208,7 @@ fn on_data<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let xml_constructor = activation.context.avm1.prototypes().xml_constructor; + let xml_constructor = activation.prototypes().xml_constructor; if let Ok(xml) = xml_constructor.construct(activation, args) { let _ = this.call_method(istr!("onXML"), &[xml], activation, ExecutionReason::Special)?; diff --git a/core/src/avm1/object/stage_object.rs b/core/src/avm1/object/stage_object.rs index 49bc83248028..8965436c9f4b 100644 --- a/core/src/avm1/object/stage_object.rs +++ b/core/src/avm1/object/stage_object.rs @@ -159,7 +159,7 @@ fn resolve_path_property<'gc>( .unwrap_or(Value::Undefined), ); } else if name.eq_with_case(b"_global", case_sensitive) { - return Some(activation.context.avm1.global_object().into()); + return Some(activation.global_object().into()); } // Resolve level names `_levelN`. diff --git a/core/src/avm1/runtime.rs b/core/src/avm1/runtime.rs index 8c847925c372..c9afbc2e09dc 100644 --- a/core/src/avm1/runtime.rs +++ b/core/src/avm1/runtime.rs @@ -16,6 +16,40 @@ use std::borrow::Cow; use swf::avm1::read::Reader; use tracing::instrument; +/// The global environment. +/// +/// Because SWFs v6 and v7+ use different case-sensitivity rules, Flash +/// keeps two environments, one case-sensitive, the other not (for an +/// example, see the `global_swf6_7_8` test). +#[derive(Collect)] +#[collect(no_drop)] +struct GlobalEnv<'gc> { + /// The global scope (pre-allocated so that it can be reused by fresh `Activation`s). + global_scope: Gc<'gc, Scope<'gc>>, + + /// System built-ins that we use internally to construct new objects. + prototypes: avm1::globals::SystemPrototypes<'gc>, + + /// Cached functions for the AsBroadcaster. + broadcaster_functions: BroadcasterFunctions<'gc>, + + /// The mappings between symbol names and constructors registered + /// with `Object.registerClass()`. This is either case-sensitive or case-insensitive. + constructor_registry: PropertyMap<'gc, Object<'gc>>, +} + +impl<'gc> GlobalEnv<'gc> { + fn create(context: &mut StringContext<'gc>) -> Self { + let (prototypes, globals, broadcaster_functions) = create_globals(context); + Self { + global_scope: Gc::new(context.gc(), Scope::from_global_object(globals)), + prototypes, + broadcaster_functions, + constructor_registry: PropertyMap::new(), + } + } +} + #[derive(Collect)] #[collect(no_drop)] pub struct Avm1<'gc> { @@ -26,14 +60,9 @@ pub struct Avm1<'gc> { /// don't close over the constant pool they were defined with. constant_pool: Gc<'gc, Vec>>, - /// The global scope (pre-allocated so that it can be reused by fresh `Activation`s). - global_scope: Gc<'gc, Scope<'gc>>, - - /// System built-ins that we use internally to construct new objects. - prototypes: avm1::globals::SystemPrototypes<'gc>, - - /// Cached functions for the AsBroadcaster - broadcaster_functions: BroadcasterFunctions<'gc>, + /// The global environment, dependent on the ambient SWF version. + env_case_sensitive: GlobalEnv<'gc>, + env_case_insensitive: GlobalEnv<'gc>, /// DisplayObject property map. display_properties: stage_object::DisplayPropertyMap<'gc>, @@ -60,13 +89,6 @@ pub struct Avm1<'gc> { /// The list of all movie clips in execution order. clip_exec_list: Option>, - /// The mappings between symbol names and constructors registered - /// with `Object.registerClass()`. - /// Because SWFs v6 and v7+ use different case-sensitivity rules, Flash - /// keeps two separate registries, one case-sensitive, the other not. - constructor_registry_case_insensitive: PropertyMap<'gc, Object<'gc>>, - constructor_registry_case_sensitive: PropertyMap<'gc, Object<'gc>>, - /// If getBounds / getRect is called on a MovieClip with invalid bounds and the /// target space is identical to the origin space, but the target is not the /// MovieClip itself, the call can return either the default invalid rectangle @@ -91,14 +113,12 @@ pub struct Avm1<'gc> { impl<'gc> Avm1<'gc> { pub fn new(context: &mut StringContext<'gc>, player_version: u8) -> Self { let gc_context = context.gc(); - let (prototypes, globals, broadcaster_functions) = create_globals(context); Self { player_version, constant_pool: Gc::new(gc_context, vec![]), - global_scope: Gc::new(gc_context, Scope::from_global_object(globals)), - prototypes, - broadcaster_functions, + env_case_insensitive: GlobalEnv::create(context), + env_case_sensitive: GlobalEnv::create(context), display_properties: stage_object::DisplayPropertyMap::new(context), stack: vec![], registers: [ @@ -111,8 +131,6 @@ impl<'gc> Avm1<'gc> { max_recursion_depth: 255, has_mouse_listener: false, clip_exec_list: None, - constructor_registry_case_insensitive: PropertyMap::new(), - constructor_registry_case_sensitive: PropertyMap::new(), #[cfg(feature = "avm_debug")] debug_output: false, @@ -186,7 +204,7 @@ impl<'gc> Avm1<'gc> { let child_scope = Gc::new( action_context.gc(), Scope::new( - action_context.avm1.global_scope, + action_context.avm1.global_scope(active_clip.swf_version()), scope::ScopeClass::Target, clip_obj, ), @@ -292,8 +310,6 @@ impl<'gc> Avm1<'gc> { ); let broadcaster = activation - .context - .avm1 .global_object() .get(broadcaster_name, &mut activation) .unwrap() @@ -352,19 +368,27 @@ impl<'gc> Avm1<'gc> { value } - /// Obtain a reference to `_global`. - pub fn global_object(&self) -> Object<'gc> { - self.global_scope.locals_cell() + #[inline(always)] + pub fn is_case_sensitive(swf_version: u8) -> bool { + swf_version >= 7 } /// Obtain a reference to the global scope. - pub fn global_scope(&self) -> Gc<'gc, Scope<'gc>> { - self.global_scope + pub fn global_scope(&self, swf_version: u8) -> Gc<'gc, Scope<'gc>> { + if Self::is_case_sensitive(swf_version) { + self.env_case_sensitive.global_scope + } else { + self.env_case_insensitive.global_scope + } } /// Obtain system built-in prototypes for this instance. - pub fn prototypes(&self) -> &avm1::globals::SystemPrototypes<'gc> { - &self.prototypes + pub fn prototypes(&self, swf_version: u8) -> &avm1::globals::SystemPrototypes<'gc> { + if Self::is_case_sensitive(swf_version) { + &self.env_case_sensitive.prototypes + } else { + &self.env_case_insensitive.prototypes + } } /// Obtains the constant pool to use for new activations from code sources that @@ -392,8 +416,12 @@ impl<'gc> Avm1<'gc> { self.max_recursion_depth = max_recursion_depth } - pub fn broadcaster_functions(&self) -> BroadcasterFunctions<'gc> { - self.broadcaster_functions + pub fn broadcaster_functions(&self, swf_version: u8) -> BroadcasterFunctions<'gc> { + if Self::is_case_sensitive(swf_version) { + self.env_case_sensitive.broadcaster_functions + } else { + self.env_case_insensitive.broadcaster_functions + } } /// The Flash Player version we're emulating. @@ -514,11 +542,11 @@ impl<'gc> Avm1<'gc> { swf_version: u8, symbol: AvmString<'gc>, ) -> Option> { - let is_case_sensitive = swf_version >= 7; + let is_case_sensitive = Self::is_case_sensitive(swf_version); let registry = if is_case_sensitive { - &self.constructor_registry_case_sensitive + &self.env_case_sensitive.constructor_registry } else { - &self.constructor_registry_case_insensitive + &self.env_case_insensitive.constructor_registry }; registry.get(symbol, is_case_sensitive).copied() } @@ -529,11 +557,11 @@ impl<'gc> Avm1<'gc> { symbol: AvmString<'gc>, constructor: Option>, ) { - let is_case_sensitive = swf_version >= 7; + let is_case_sensitive = Self::is_case_sensitive(swf_version); let registry = if is_case_sensitive { - &mut self.constructor_registry_case_sensitive + &mut self.env_case_sensitive.constructor_registry } else { - &mut self.constructor_registry_case_insensitive + &mut self.env_case_insensitive.constructor_registry }; if let Some(constructor) = constructor { registry.insert(symbol, constructor, is_case_sensitive); diff --git a/core/src/avm1/value.rs b/core/src/avm1/value.rs index 700ca836a5bd..cb972c5b59a3 100644 --- a/core/src/avm1/value.rs +++ b/core/src/avm1/value.rs @@ -473,9 +473,9 @@ impl<'gc> Value<'gc> { } // Else, select the correct prototype for it from the system prototypes list. Value::Null | Value::Undefined => (self, None), - Value::Bool(_) => (self, Some(activation.context.avm1.prototypes().boolean)), - Value::Number(_) => (self, Some(activation.context.avm1.prototypes().number)), - Value::String(_) => (self, Some(activation.context.avm1.prototypes().string)), + Value::Bool(_) => (self, Some(activation.prototypes().boolean)), + Value::Number(_) => (self, Some(activation.prototypes().number)), + Value::String(_) => (self, Some(activation.prototypes().string)), }; let obj = Object::new(&activation.context.strings, proto); diff --git a/core/src/avm1/xml/tree.rs b/core/src/avm1/xml/tree.rs index 9aa1474c825a..b9e26db6a655 100644 --- a/core/src/avm1/xml/tree.rs +++ b/core/src/avm1/xml/tree.rs @@ -357,7 +357,7 @@ impl<'gc> XmlNode<'gc> { match self.get_script_object() { Some(object) => object, None => { - let xml_node = activation.context.avm1.prototypes().xml_node_constructor; + let xml_node = activation.prototypes().xml_node_constructor; let prototype = xml_node .get(istr!("prototype"), activation) .map(|p| p.coerce_to_object(activation)) diff --git a/core/src/display_object/avm1_button.rs b/core/src/display_object/avm1_button.rs index 263fefe15fbe..58546c89b780 100644 --- a/core/src/display_object/avm1_button.rs +++ b/core/src/display_object/avm1_button.rs @@ -274,7 +274,7 @@ impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> { if self.0.object.get().is_none() { let object = Object::new_with_native( &context.strings, - Some(context.avm1.prototypes().button), + Some(context.avm1.prototypes(self.swf_version()).button), NativeObject::Button(self), ); let obj = unlock!(Gc::write(context.gc(), self.0), Avm1ButtonData, object); diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index 4e51245c1038..c075d4204d15 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -2101,11 +2101,15 @@ impl<'gc> EditText<'gc> { fn initialize_as_broadcaster(self, activation: &mut Avm1Activation<'_, 'gc>) { if let Avm1Value::Object(object) = self.object() { - activation.context.avm1.broadcaster_functions().initialize( - &activation.context.strings, - object, - activation.context.avm1.prototypes().array, - ); + activation + .context + .avm1 + .broadcaster_functions(activation.swf_version()) + .initialize( + &activation.context.strings, + object, + activation.prototypes().array, + ); if let Ok(Avm1Value::Object(listeners)) = object.get(istr!("_listeners"), activation) { let length = listeners.length(activation); @@ -2156,7 +2160,7 @@ impl<'gc> EditText<'gc> { if self.0.object.get().is_none() { let object = Avm1Object::new_with_native( &context.strings, - Some(context.avm1.prototypes().text_field), + Some(context.avm1.prototypes(self.swf_version()).text_field), Avm1NativeObject::EditText(self), ); diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 36f37ea0377c..c96c20dc96df 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -1746,7 +1746,7 @@ impl<'gc> MovieClip<'gc> { let object = Avm1Object::new_with_native( &context.strings, - Some(context.avm1.prototypes().movie_clip), + Some(context.avm1.prototypes(self.swf_version()).movie_clip), Avm1NativeObject::MovieClip(self), ); let write = Gc::write(context.gc(), self.0); diff --git a/core/src/display_object/video.rs b/core/src/display_object/video.rs index 0d22d4fa8e8b..f69f7f32ecd7 100644 --- a/core/src/display_object/video.rs +++ b/core/src/display_object/video.rs @@ -429,7 +429,7 @@ impl<'gc> TDisplayObject<'gc> for Video<'gc> { if self.0.object.get().is_none() && !movie.is_action_script_3() { let object = Avm1Object::new_with_native( &context.strings, - Some(context.avm1.prototypes().video), + Some(context.avm1.prototypes(self.swf_version()).video), Avm1NativeObject::Video(self), ); self.set_object(context, object.into()); diff --git a/core/src/external.rs b/core/src/external.rs index c6ab6dcc5a70..9c5d9b5f4966 100644 --- a/core/src/external.rs +++ b/core/src/external.rs @@ -171,7 +171,7 @@ impl Value { Value::Object(values) => { let object = Avm1Object::new( &activation.context.strings, - Some(activation.context.avm1.prototypes().object), + Some(activation.prototypes().object), ); for (key, value) in values { let key = AvmString::new_utf8(activation.gc(), key); diff --git a/core/src/player.rs b/core/src/player.rs index 4e643457f770..67a59c42d703 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -1084,7 +1084,7 @@ impl Player { dumper.print_variables( "Global Variables:", "_global", - activation.context.avm1.global_object(), + activation.global_object(), &mut activation, ); diff --git a/core/src/streams.rs b/core/src/streams.rs index 4a45d2f5f969..863c72fc52da 100644 --- a/core/src/streams.rs +++ b/core/src/streams.rs @@ -1309,12 +1309,12 @@ impl<'gc> NetStream<'gc> { match object { Some(NetStreamKind::Avm1(object)) => { let root = context.stage.root_clip().expect("root"); - let object_proto = context.avm1.prototypes().object; let mut activation = Avm1Activation::from_nothing( context, Avm1ActivationIdentifier::root("[NetStream Status Event]"), root, ); + let object_proto = activation.prototypes().object; let info_object = Avm1Object::new(&activation.context.strings, Some(object_proto)); for (key, value) in values { diff --git a/tests/tests/swfs/avm1/global_swf6_7_8/Child6.as b/tests/tests/swfs/avm1/global_swf6_7_8/Child6.as new file mode 100644 index 000000000000..820f1fb683d7 --- /dev/null +++ b/tests/tests/swfs/avm1/global_swf6_7_8/Child6.as @@ -0,0 +1,10 @@ +// Compile with: +// mtasc -main -header 200:150:30 -version 6 Child6.as -swf child6.swf + +class Child6 { + static function main(current) { + current.global = _global; + current.anObject = {}; + current.anArray = [1, 2, 3]; + } +} diff --git a/tests/tests/swfs/avm1/global_swf6_7_8/Child7.as b/tests/tests/swfs/avm1/global_swf6_7_8/Child7.as new file mode 100644 index 000000000000..5c26d813a99a --- /dev/null +++ b/tests/tests/swfs/avm1/global_swf6_7_8/Child7.as @@ -0,0 +1,8 @@ +// Compile with: +// mtasc -main -header 200:150:30 -version 7 Child7.as -swf child7.swf + +class Child7 { + static function main(current) { + current.global = _global; + } +} diff --git a/tests/tests/swfs/avm1/global_swf6_7_8/Test.as b/tests/tests/swfs/avm1/global_swf6_7_8/Test.as new file mode 100644 index 000000000000..41f58e57227b --- /dev/null +++ b/tests/tests/swfs/avm1/global_swf6_7_8/Test.as @@ -0,0 +1,59 @@ +// Compile with: +// mtasc -main -header 200:150:30 -version 8 Test.as -swf test.swf + +class Test { + static function main(current) { + var mc6 = current.createEmptyMovieClip("child6", 1); + var loader6 = new MovieClipLoader(); + loader6.loadClip("child6.swf", mc6); + + var mc7 = current.createEmptyMovieClip("child7", 2); + var loader7 = new MovieClipLoader(); + loader7.loadClip("child7.swf", mc7); + + var nbToLoad = 2; + var listener = { + onLoadInit: function() { + if (--nbToLoad == 0) { + Test.test(mc6, mc7); + } + } + }; + + loader6.addListener(listener); + loader7.addListener(listener); + } + + static function test(swf6, swf7) { + var g6 = swf6.global; + var g7 = swf7.global; + var g8 = _global; + + trace("typeof g6: " + (typeof g6)); + trace("typeof g7: " + (typeof g7)); + trace("typeof g8: " + (typeof g8)); + + // v7 and v8 SWFs have the same _global object + trace("g7 === g8: " + (g7 === g8)); + + // v6 and v7 SWFs have completely separate _global objects + trace("g6 === g7: " + (g6 === g7)); + trace("typeof g6.Object.prototype: " + (typeof g6.Object.prototype)); + trace("typeof g7.Object.prototype: " + (typeof g7.Object.prototype)); + trace("g6.Object.prototype === g7.Object.prototype: " + (g6.Object.prototype === g7.Object.prototype)); + + // ...but case-sensitivity doesn't carry over. + trace("g6.OBJECT: " + g6.OBJECT); + + // Objects created in a v6 SWF uses the classes from their _global + var anObject = {}, anArray = []; + trace("swf6.anObject: " + swf6.anObject); + trace("swf6.anObject instanceof g6.Object: " + (swf6.anObject instanceof g6.Object)); + trace("swf6.anObject instanceof g7.Object: " + (swf6.anObject instanceof g7.Object)); + trace("swf6.anArray: " + swf6.anArray); + trace("swf6.anArray instanceof g6.Array: " + (swf6.anArray instanceof g6.Array)); + trace("swf6.anArray instanceof g7.Array: " + (swf6.anArray instanceof g7.Array)); + + fscommand("quit"); + } +} diff --git a/tests/tests/swfs/avm1/global_swf6_7_8/child6.swf b/tests/tests/swfs/avm1/global_swf6_7_8/child6.swf new file mode 100644 index 000000000000..31071c643d75 Binary files /dev/null and b/tests/tests/swfs/avm1/global_swf6_7_8/child6.swf differ diff --git a/tests/tests/swfs/avm1/global_swf6_7_8/child7.swf b/tests/tests/swfs/avm1/global_swf6_7_8/child7.swf new file mode 100644 index 000000000000..643d21a0db10 Binary files /dev/null and b/tests/tests/swfs/avm1/global_swf6_7_8/child7.swf differ diff --git a/tests/tests/swfs/avm1/global_swf6_7_8/output.txt b/tests/tests/swfs/avm1/global_swf6_7_8/output.txt new file mode 100644 index 000000000000..772227157587 --- /dev/null +++ b/tests/tests/swfs/avm1/global_swf6_7_8/output.txt @@ -0,0 +1,15 @@ +typeof g6: object +typeof g7: object +typeof g8: object +g7 === g8: true +g6 === g7: false +typeof g6.Object.prototype: object +typeof g7.Object.prototype: object +g6.Object.prototype === g7.Object.prototype: false +g6.OBJECT: undefined +swf6.anObject: [object Object] +swf6.anObject instanceof g6.Object: true +swf6.anObject instanceof g7.Object: false +swf6.anArray: 1,2,3 +swf6.anArray instanceof g6.Array: true +swf6.anArray instanceof g7.Array: false diff --git a/tests/tests/swfs/avm1/global_swf6_7_8/test.swf b/tests/tests/swfs/avm1/global_swf6_7_8/test.swf new file mode 100644 index 000000000000..5f0998d66fc3 Binary files /dev/null and b/tests/tests/swfs/avm1/global_swf6_7_8/test.swf differ diff --git a/tests/tests/swfs/avm1/global_swf6_7_8/test.toml b/tests/tests/swfs/avm1/global_swf6_7_8/test.toml new file mode 100644 index 000000000000..ded2e363a928 --- /dev/null +++ b/tests/tests/swfs/avm1/global_swf6_7_8/test.toml @@ -0,0 +1 @@ +num_frames = 2