diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 0b3a956079bb..fdc94c263bc2 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1559,7 +1559,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { ScriptObject::catch_scope(self, &vname) } else { // for `finally` scopes, FP just creates a normal object. - self.avm2().classes().object.construct(self, &[])? + ScriptObject::new_object(self) }; self.push_stack(so); @@ -1813,13 +1813,13 @@ impl<'a, 'gc> Activation<'a, 'gc> { } fn op_new_object(&mut self, num_args: u32) -> Result, Error<'gc>> { - let object = self.context.avm2.classes().object.construct(self, &[])?; + let object = ScriptObject::new_object(self); for _ in 0..num_args { let value = self.pop_stack(); let name = self.pop_stack(); - object.set_public_property(name.coerce_to_string(self)?, value, self)?; + object.set_string_property_local(name.coerce_to_string(self)?, value, self)?; } self.push_stack(object); @@ -1835,7 +1835,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let method_entry = self.table_method(method, index, true)?; let scope = self.create_scopechain(); - let new_fn = FunctionObject::from_function(self, method_entry, scope)?; + let new_fn = FunctionObject::from_method(self, method_entry, scope, None, None, None); self.push_stack(new_fn); @@ -2527,13 +2527,15 @@ impl<'a, 'gc> Activation<'a, 'gc> { let object = self.pop_stack(); if matches!(object, Value::Undefined | Value::Null) { self.push_raw(0.0); + } else if let Some(next_index) = object + .as_object() + .map(|o| o.get_next_enumerant(cur_index, self)) + .transpose()? + .flatten() + { + self.push_raw(next_index); } else { - let object = object.coerce_to_object(self)?; - if let Some(next_index) = object.get_next_enumerant(cur_index, self)? { - self.push_raw(next_index); - } else { - self.push_raw(0.0); - } + self.push_raw(0.0); } Ok(FrameControl::Continue) @@ -2689,16 +2691,14 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value = self.pop_stack(); - if let Ok(value) = value.coerce_to_object(self) { - let is_instance_of = value.is_instance_of(self, type_object)?; + match value { + Value::Undefined => return Err(make_null_or_undefined_error(self, value, None)), + Value::Null => self.push_raw(false), + value => { + let is_instance_of = value.is_instance_of(self, type_object); - self.push_raw(is_instance_of); - } else if matches!(value, Value::Undefined) { - // undefined - return Err(make_null_or_undefined_error(self, value, None)); - } else { - // null - self.push_raw(false); + self.push_raw(is_instance_of); + } } Ok(FrameControl::Continue) diff --git a/core/src/avm2/amf.rs b/core/src/avm2/amf.rs index 1612a1b0cef0..dec582e0fa42 100644 --- a/core/src/avm2/amf.rs +++ b/core/src/avm2/amf.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use super::property::Property; use crate::avm2::bytearray::ByteArrayStorage; use crate::avm2::class::Class; -use crate::avm2::object::{ByteArrayObject, ClassObject, TObject, VectorObject}; +use crate::avm2::object::{ByteArrayObject, ClassObject, ScriptObject, TObject, VectorObject}; use crate::avm2::vector::VectorStorage; use crate::avm2::ArrayObject; use crate::avm2::ArrayStorage; @@ -315,7 +315,7 @@ pub fn deserialize_value_impl<'gc>( // Now let's add each element as a property for element in elements { - array.set_public_property( + array.set_string_property_local( AvmString::new_utf8(activation.context.gc_context, element.name()), deserialize_value_impl(activation, element.value(), object_map)?, activation, @@ -473,7 +473,7 @@ pub fn deserialize_value_impl<'gc>( dict_obj.set_property_by_object(key, value, activation.context.gc_context); } else { let key_string = key.coerce_to_string(activation)?; - dict_obj.set_public_property(key_string, value, activation)?; + dict_obj.set_string_property_local(key_string, value, activation)?; } } dict_obj.into() @@ -507,14 +507,10 @@ pub fn deserialize_lso<'gc>( activation: &mut Activation<'_, 'gc>, lso: &Lso, ) -> Result, Error<'gc>> { - let obj = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; + let obj = ScriptObject::new_object(activation); for child in &lso.body { - obj.set_public_property( + obj.set_string_property_local( AvmString::new_utf8(activation.context.gc_context, &child.name), deserialize_value(activation, child.value())?, activation, diff --git a/core/src/avm2/e4x.rs b/core/src/avm2/e4x.rs index ac630b47be15..fb8b3981f309 100644 --- a/core/src/avm2/e4x.rs +++ b/core/src/avm2/e4x.rs @@ -1,9 +1,12 @@ -use std::{ - cell::{Ref, RefMut}, - fmt::{self, Debug}, -}; +use crate::avm2::error::{make_error_1010, make_error_1085, make_error_1118, type_error}; +use crate::avm2::globals::slots::xml as xml_class_slots; +use crate::avm2::object::{E4XOrXml, FunctionObject, NamespaceObject}; +use crate::avm2::{Activation, Error, Multiname, TObject, Value}; +use crate::string::{AvmString, WStr, WString}; +use crate::xml::custom_unescape; use gc_arena::{Collect, GcCell, Mutation}; + use quick_xml::{ errors::{IllFormedError, SyntaxError as XmlSyntaxError}, events::{attributes::AttrError as XmlAttrError, BytesStart, Event}, @@ -11,15 +14,8 @@ use quick_xml::{ Error as XmlError, NsReader, }; -use crate::{avm2::TObject, xml::custom_unescape}; - -use super::{ - error::{make_error_1010, make_error_1085, make_error_1118, type_error}, - object::{E4XOrXml, FunctionObject, NamespaceObject}, - string::AvmString, - Activation, Error, Multiname, Value, -}; -use crate::string::{WStr, WString}; +use std::cell::{Ref, RefMut}; +use std::fmt::{self, Debug}; mod is_xml_name; mod iterators; @@ -1664,16 +1660,15 @@ pub fn to_xml_string<'gc>( .avm2() .classes() .xml - .get_public_property("prettyPrinting", activation) - .expect("prettyPrinting should be set") + .get_slot(xml_class_slots::PRETTY_PRINTING) .coerce_to_boolean(); + let pretty = if pretty_printing { let pretty_indent = activation .avm2() .classes() .xml - .get_public_property("prettyIndent", activation) - .expect("prettyIndent should be set") + .get_slot(xml_class_slots::PRETTY_INDENT) .coerce_to_i32(activation) .expect("shouldn't error"); diff --git a/core/src/avm2/filters.rs b/core/src/avm2/filters.rs index 45bd34619cbf..669346d5978b 100644 --- a/core/src/avm2/filters.rs +++ b/core/src/avm2/filters.rs @@ -10,6 +10,7 @@ use swf::{ }; use crate::avm2::error::{make_error_2008, type_error}; +use crate::avm2::globals::slots::flash_geom_point as point_slots; use crate::avm2::{Activation, ArrayObject, ClassObject, Error, Object, TObject, Value}; use super::globals::flash::display::shader_job::get_shader_args; @@ -458,12 +459,8 @@ fn avm2_to_displacement_map_filter<'gc>( let map_point = if let Value::Object(point) = object.get_public_property("mapPoint", activation)? { ( - point - .get_public_property("x", activation)? - .coerce_to_i32(activation)?, - point - .get_public_property("y", activation)? - .coerce_to_i32(activation)?, + point.get_slot(point_slots::X).coerce_to_i32(activation)?, + point.get_slot(point_slots::Y).coerce_to_i32(activation)?, ) } else { (0, 0) diff --git a/core/src/avm2/flv.rs b/core/src/avm2/flv.rs index 880bf65f87f1..70129436372e 100644 --- a/core/src/avm2/flv.rs +++ b/core/src/avm2/flv.rs @@ -21,7 +21,7 @@ fn avm2_object_from_flv_variables<'gc>( let property_name = value.name; info_object - .set_public_property( + .set_string_property_local( AvmString::new_utf8_bytes(activation.context.gc_context, property_name), value.data.to_avm2_value(activation), activation, diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index a077bfa5dd31..9a3027461275 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -210,6 +210,7 @@ pub struct SystemClassDefs<'gc> { pub rectangletexture: Class<'gc>, pub display_object: Class<'gc>, pub sprite: Class<'gc>, + pub contextmenuitem: Class<'gc>, } impl<'gc> SystemClasses<'gc> { @@ -380,6 +381,7 @@ impl<'gc> SystemClassDefs<'gc> { rectangletexture: object, display_object: object, sprite: object, + contextmenuitem: object, } } } @@ -1004,6 +1006,7 @@ pub fn init_native_system_classes(activation: &mut Activation<'_, '_>) { "RectangleTexture", rectangletexture ), + ("flash.ui", "ContextMenuItem", contextmenuitem), ] ); } diff --git a/core/src/avm2/globals/XML.as b/core/src/avm2/globals/XML.as index 251e5cb53d21..b935c190b168 100644 --- a/core/src/avm2/globals/XML.as +++ b/core/src/avm2/globals/XML.as @@ -106,7 +106,11 @@ package { public static var ignoreComments:Boolean = true; public static var ignoreProcessingInstructions:Boolean = true; public static var ignoreWhitespace:Boolean = true; + + [Ruffle(InternalSlot)] public static var prettyPrinting:Boolean = true; + + [Ruffle(InternalSlot)] public static var prettyIndent:int = 2; prototype.hasComplexContent = function():Boolean { diff --git a/core/src/avm2/globals/avmplus.rs b/core/src/avm2/globals/avmplus.rs index 0a7e9a7d13fb..16a71ef88005 100644 --- a/core/src/avm2/globals/avmplus.rs +++ b/core/src/avm2/globals/avmplus.rs @@ -2,7 +2,7 @@ use crate::avm2::class::Class; pub use crate::avm2::globals::flash::utils::get_qualified_class_name; use crate::avm2::metadata::Metadata; use crate::avm2::method::Method; -use crate::avm2::object::{ArrayObject, TObject}; +use crate::avm2::object::{ArrayObject, ScriptObject, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::property::Property; use crate::avm2::{Activation, Error, Multiname, Namespace, Object, Value}; @@ -22,11 +22,7 @@ pub fn describe_type_json<'gc>( let value = args[0]; let class_def = instance_class_describe_type(activation, value); - let object = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; + let object = ScriptObject::new_object(activation); let mut used_class_def = class_def; if flags.contains(DescribeTypeFlags::USE_ITRAITS) { @@ -41,15 +37,15 @@ pub fn describe_type_json<'gc>( .dollar_removed_name(activation.context.gc_context) .to_qualified_name(activation.context.gc_context); - object.set_public_property("name", qualified_name.into(), activation)?; + object.set_string_property_local("name", qualified_name.into(), activation)?; - object.set_public_property( + object.set_string_property_local( "isDynamic", (!used_class_def.is_sealed()).into(), activation, )?; - object.set_public_property("isFinal", used_class_def.is_final().into(), activation)?; - object.set_public_property( + object.set_string_property_local("isFinal", used_class_def.is_final().into(), activation)?; + object.set_string_property_local( "isStatic", value .as_object() @@ -61,9 +57,9 @@ pub fn describe_type_json<'gc>( let traits = describe_internal_body(activation, used_class_def, flags)?; if flags.contains(DescribeTypeFlags::INCLUDE_TRAITS) { - object.set_public_property("traits", traits.into(), activation)?; + object.set_string_property_local("traits", traits.into(), activation)?; } else { - object.set_public_property("traits", Value::Null, activation)?; + object.set_string_property_local("traits", Value::Null, activation)?; } Ok(object.into()) @@ -93,11 +89,7 @@ fn describe_internal_body<'gc>( ) -> Result, Error<'gc>> { let mc = activation.gc(); - let traits = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; + let traits = ScriptObject::new_object(activation); let bases = ArrayObject::empty(activation)?.as_array_object().unwrap(); let interfaces = ArrayObject::empty(activation)?.as_array_object().unwrap(); @@ -106,33 +98,33 @@ fn describe_internal_body<'gc>( let methods = ArrayObject::empty(activation)?.as_array_object().unwrap(); if flags.contains(DescribeTypeFlags::INCLUDE_BASES) { - traits.set_public_property("bases", bases.into(), activation)?; + traits.set_string_property_local("bases", bases.into(), activation)?; } else { - traits.set_public_property("bases", Value::Null, activation)?; + traits.set_string_property_local("bases", Value::Null, activation)?; } if flags.contains(DescribeTypeFlags::INCLUDE_INTERFACES) { - traits.set_public_property("interfaces", interfaces.into(), activation)?; + traits.set_string_property_local("interfaces", interfaces.into(), activation)?; } else { - traits.set_public_property("interfaces", Value::Null, activation)?; + traits.set_string_property_local("interfaces", Value::Null, activation)?; } if flags.contains(DescribeTypeFlags::INCLUDE_VARIABLES) { - traits.set_public_property("variables", variables.into(), activation)?; + traits.set_string_property_local("variables", variables.into(), activation)?; } else { - traits.set_public_property("variables", Value::Null, activation)?; + traits.set_string_property_local("variables", Value::Null, activation)?; } if flags.contains(DescribeTypeFlags::INCLUDE_ACCESSORS) { - traits.set_public_property("accessors", accessors.into(), activation)?; + traits.set_string_property_local("accessors", accessors.into(), activation)?; } else { - traits.set_public_property("accessors", Value::Null, activation)?; + traits.set_string_property_local("accessors", Value::Null, activation)?; } if flags.contains(DescribeTypeFlags::INCLUDE_METHODS) { - traits.set_public_property("methods", methods.into(), activation)?; + traits.set_string_property_local("methods", methods.into(), activation)?; } else { - traits.set_public_property("methods", Value::Null, activation)?; + traits.set_string_property_local("methods", Value::Null, activation)?; } let mut bases_array = bases.as_array_storage_mut(mc).unwrap(); @@ -216,28 +208,28 @@ fn describe_internal_body<'gc>( let trait_metadata = vtable.get_metadata_for_slot(slot_id); - let variable = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; - variable.set_public_property("name", prop_name.into(), activation)?; - variable.set_public_property("type", prop_class_name.into(), activation)?; - variable.set_public_property("access", access.into(), activation)?; - variable.set_public_property( + let variable = ScriptObject::new_object(activation); + variable.set_string_property_local("name", prop_name.into(), activation)?; + variable.set_string_property_local("type", prop_class_name.into(), activation)?; + variable.set_string_property_local("access", access.into(), activation)?; + variable.set_string_property_local( "uri", uri.map_or(Value::Null, |u| u.into()), activation, )?; - variable.set_public_property("metadata", Value::Null, activation)?; + variable.set_string_property_local("metadata", Value::Null, activation)?; if flags.contains(DescribeTypeFlags::INCLUDE_METADATA) { let metadata_object = ArrayObject::empty(activation)?; if let Some(metadata) = trait_metadata { write_metadata(metadata_object, &metadata, activation)?; } - variable.set_public_property("metadata", metadata_object.into(), activation)?; + variable.set_string_property_local( + "metadata", + metadata_object.into(), + activation, + )?; } variables_array.push(variable.into()); @@ -274,41 +266,37 @@ fn describe_internal_body<'gc>( let trait_metadata = vtable.get_metadata_for_disp(disp_id); - let method_obj = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; + let method_obj = ScriptObject::new_object(activation); - method_obj.set_public_property("name", prop_name.into(), activation)?; - method_obj.set_public_property( + method_obj.set_string_property_local("name", prop_name.into(), activation)?; + method_obj.set_string_property_local( "returnType", return_type_name.into(), activation, )?; - method_obj.set_public_property( + method_obj.set_string_property_local( "declaredBy", declared_by_name.into(), activation, )?; - method_obj.set_public_property( + method_obj.set_string_property_local( "uri", uri.map_or(Value::Null, |u| u.into()), activation, )?; let params = write_params(&method.method, activation)?; - method_obj.set_public_property("parameters", params.into(), activation)?; + method_obj.set_string_property_local("parameters", params.into(), activation)?; - method_obj.set_public_property("metadata", Value::Null, activation)?; + method_obj.set_string_property_local("metadata", Value::Null, activation)?; if flags.contains(DescribeTypeFlags::INCLUDE_METADATA) { let metadata_object = ArrayObject::empty(activation)?; if let Some(metadata) = trait_metadata { write_metadata(metadata_object, &metadata, activation)?; } - method_obj.set_public_property( + method_obj.set_string_property_local( "metadata", metadata_object.into(), activation, @@ -356,16 +344,16 @@ fn describe_internal_body<'gc>( let accessor_type = display_name(activation.strings(), method_type); let declared_by = defining_class.dollar_removed_name(mc).to_qualified_name(mc); - let accessor_obj = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; - accessor_obj.set_public_property("name", prop_name.into(), activation)?; - accessor_obj.set_public_property("access", access.into(), activation)?; - accessor_obj.set_public_property("type", accessor_type.into(), activation)?; - accessor_obj.set_public_property("declaredBy", declared_by.into(), activation)?; - accessor_obj.set_public_property( + let accessor_obj = ScriptObject::new_object(activation); + accessor_obj.set_string_property_local("name", prop_name.into(), activation)?; + accessor_obj.set_string_property_local("access", access.into(), activation)?; + accessor_obj.set_string_property_local("type", accessor_type.into(), activation)?; + accessor_obj.set_string_property_local( + "declaredBy", + declared_by.into(), + activation, + )?; + accessor_obj.set_string_property_local( "uri", uri.map_or(Value::Null, |u| u.into()), activation, @@ -388,13 +376,13 @@ fn describe_internal_body<'gc>( if flags.contains(DescribeTypeFlags::INCLUDE_METADATA) && metadata_object.as_array_storage().unwrap().length() > 0 { - accessor_obj.set_public_property( + accessor_obj.set_string_property_local( "metadata", metadata_object.into(), activation, )?; } else { - accessor_obj.set_public_property("metadata", Value::Null, activation)?; + accessor_obj.set_string_property_local("metadata", Value::Null, activation)?; } accessors_array.push(accessor_obj.into()); @@ -407,10 +395,10 @@ fn describe_internal_body<'gc>( if flags.contains(DescribeTypeFlags::INCLUDE_CONSTRUCTOR) && !constructor.signature().is_empty() { let params = write_params(&constructor, activation)?; - traits.set_public_property("constructor", params.into(), activation)?; + traits.set_string_property_local("constructor", params.into(), activation)?; } else { // This is needed to override the normal 'constructor' property - traits.set_public_property("constructor", Value::Null, activation)?; + traits.set_string_property_local("constructor", Value::Null, activation)?; } if flags.contains(DescribeTypeFlags::INCLUDE_METADATA) { @@ -422,9 +410,9 @@ fn describe_internal_body<'gc>( ); let metadata_object = ArrayObject::empty(activation)?; - traits.set_public_property("metadata", metadata_object.into(), activation)?; + traits.set_string_property_local("metadata", metadata_object.into(), activation)?; } else { - traits.set_public_property("metadata", Value::Null, activation)?; + traits.set_string_property_local("metadata", Value::Null, activation)?; } Ok(traits) @@ -452,13 +440,9 @@ fn write_params<'gc>( for param in method.signature() { let param_type_name = display_name(activation.strings(), param.param_type_name); let optional = param.default_value.is_some(); - let param_obj = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; - param_obj.set_public_property("type", param_type_name.into(), activation)?; - param_obj.set_public_property("optional", optional.into(), activation)?; + let param_obj = ScriptObject::new_object(activation); + param_obj.set_string_property_local("type", param_type_name.into(), activation)?; + param_obj.set_string_property_local("optional", optional.into(), activation)?; params_array.push(param_obj.into()); } Ok(params) diff --git a/core/src/avm2/globals/flash/display/GraphicsBitmapFill.as b/core/src/avm2/globals/flash/display/GraphicsBitmapFill.as index 0f7d1d92995a..2923a992be2f 100644 --- a/core/src/avm2/globals/flash/display/GraphicsBitmapFill.as +++ b/core/src/avm2/globals/flash/display/GraphicsBitmapFill.as @@ -3,9 +3,16 @@ package flash.display { import flash.geom.Matrix; public final class GraphicsBitmapFill implements IGraphicsFill, IGraphicsData { + [Ruffle(InternalSlot)] public var bitmapData : BitmapData; + + [Ruffle(InternalSlot)] public var matrix : Matrix; + + [Ruffle(InternalSlot)] public var repeat : Boolean; + + [Ruffle(InternalSlot)] public var smooth : Boolean; public function GraphicsBitmapFill(bitmapData:BitmapData = null, matrix:Matrix = null, repeat:Boolean = true, smooth:Boolean = false) { @@ -15,4 +22,4 @@ import flash.geom.Matrix; this.smooth = smooth; } } -} \ No newline at end of file +} diff --git a/core/src/avm2/globals/flash/display/GraphicsGradientFill.as b/core/src/avm2/globals/flash/display/GraphicsGradientFill.as index 7b37deaf3ba7..78a3bc53ec4a 100644 --- a/core/src/avm2/globals/flash/display/GraphicsGradientFill.as +++ b/core/src/avm2/globals/flash/display/GraphicsGradientFill.as @@ -3,13 +3,28 @@ package flash.display { import flash.geom.Matrix; public final class GraphicsGradientFill implements IGraphicsFill, IGraphicsData { + [Ruffle(InternalSlot)] public var alphas : Array; + + [Ruffle(InternalSlot)] public var colors : Array; + + [Ruffle(InternalSlot)] public var focalPointRatio : Number; + + [Ruffle(InternalSlot)] public var interpolationMethod : String; + + [Ruffle(InternalSlot)] public var matrix : Matrix; + + [Ruffle(InternalSlot)] public var ratios : Array; + + [Ruffle(InternalSlot)] public var spreadMethod : String; + + [Ruffle(InternalSlot)] public var type : String; public function GraphicsGradientFill( @@ -32,4 +47,4 @@ import flash.geom.Matrix; this.type = type; } } -} \ No newline at end of file +} diff --git a/core/src/avm2/globals/flash/display/GraphicsPath.as b/core/src/avm2/globals/flash/display/GraphicsPath.as index ce8f32755eae..98f778c3bb76 100644 --- a/core/src/avm2/globals/flash/display/GraphicsPath.as +++ b/core/src/avm2/globals/flash/display/GraphicsPath.as @@ -1,9 +1,14 @@ package flash.display { public final class GraphicsPath implements IGraphicsPath, IGraphicsData { + [Ruffle(InternalSlot)] public var commands : Vector.; + + [Ruffle(InternalSlot)] public var data : Vector.; - public var winding : String; + + [Ruffle(InternalSlot)] + private var _winding : String; public function GraphicsPath(commands:Vector. = null, data:Vector. = null, winding:String = "evenOdd") { this.commands = commands; @@ -11,6 +16,18 @@ package flash.display { this.winding = winding; } + public function get winding():String { + return this._winding; + } + + public function set winding(value:String):void { + if (value != "evenOdd" && value != "nonZero") { + throw new ArgumentError("Error #2008: Parameter winding must be one of the accepted values.", 2008); + } else { + this._winding = value; + } + } + [API("674")] // The online docs say 694, but that's a lie. This is the correct number from playerglobal.swc. public function cubicCurveTo(controlX1:Number, controlY1:Number, controlX2:Number, controlY2:Number, anchorX:Number, anchorY:Number):void { if (commands == null) { @@ -85,4 +102,4 @@ package flash.display { } } -} \ No newline at end of file +} diff --git a/core/src/avm2/globals/flash/display/GraphicsSolidFill.as b/core/src/avm2/globals/flash/display/GraphicsSolidFill.as index cb834298b5b5..c195ddfdb93f 100644 --- a/core/src/avm2/globals/flash/display/GraphicsSolidFill.as +++ b/core/src/avm2/globals/flash/display/GraphicsSolidFill.as @@ -1,7 +1,10 @@ package flash.display { public final class GraphicsSolidFill implements IGraphicsFill, IGraphicsData { + [Ruffle(InternalSlot)] public var alpha : Number = 1.0; + + [Ruffle(InternalSlot)] public var color : uint = 0; public function GraphicsSolidFill(color:uint = 0, alpha:Number = 1.0) { @@ -10,4 +13,4 @@ package flash.display { } } -} \ No newline at end of file +} diff --git a/core/src/avm2/globals/flash/display/GraphicsStroke.as b/core/src/avm2/globals/flash/display/GraphicsStroke.as index d6cb55e7e0be..662d5560f354 100644 --- a/core/src/avm2/globals/flash/display/GraphicsStroke.as +++ b/core/src/avm2/globals/flash/display/GraphicsStroke.as @@ -1,12 +1,25 @@ package flash.display { public final class GraphicsStroke implements IGraphicsStroke, IGraphicsData { + [Ruffle(InternalSlot)] public var caps : String; + + [Ruffle(InternalSlot)] public var fill : IGraphicsFill; + + [Ruffle(InternalSlot)] public var joints : String; + + [Ruffle(InternalSlot)] public var miterLimit : Number; + + [Ruffle(InternalSlot)] public var pixelHinting : Boolean; + + [Ruffle(InternalSlot)] public var scaleMode : String; + + [Ruffle(InternalSlot)] public var thickness : Number; public function GraphicsStroke( @@ -28,4 +41,4 @@ package flash.display { } } -} \ No newline at end of file +} diff --git a/core/src/avm2/globals/flash/display/GraphicsTrianglePath.as b/core/src/avm2/globals/flash/display/GraphicsTrianglePath.as index bdaa2b95ccbf..6de97c8b57bb 100644 --- a/core/src/avm2/globals/flash/display/GraphicsTrianglePath.as +++ b/core/src/avm2/globals/flash/display/GraphicsTrianglePath.as @@ -1,9 +1,16 @@ package flash.display { public final class GraphicsTrianglePath implements IGraphicsPath, IGraphicsData { + [Ruffle(InternalSlot)] public var culling : String; + + [Ruffle(InternalSlot)] public var indices : Vector.; + + [Ruffle(InternalSlot)] public var uvtData : Vector.; + + [Ruffle(InternalSlot)] public var vertices : Vector.; public function GraphicsTrianglePath(vertices:Vector. = null, indices:Vector. = null, uvtData:Vector. = null, culling:String = "none") { @@ -14,4 +21,4 @@ package flash.display { } } -} \ No newline at end of file +} diff --git a/core/src/avm2/globals/flash/display/NativeMenuItem.as b/core/src/avm2/globals/flash/display/NativeMenuItem.as index e3bea52b80c2..d9dc68391931 100644 --- a/core/src/avm2/globals/flash/display/NativeMenuItem.as +++ b/core/src/avm2/globals/flash/display/NativeMenuItem.as @@ -6,7 +6,9 @@ package flash.display { // but airglobal.swc disagrees with that: [API("667")] public class NativeMenuItem extends EventDispatcher { + [Ruffle(InternalSlot)] public var enabled: Boolean = false; + public var checked: Boolean = false; public var data: Object; public var isSeparator: Boolean; diff --git a/core/src/avm2/globals/flash/display/Shader.as b/core/src/avm2/globals/flash/display/Shader.as index 5d9a665eb9de..0acacc80fd23 100644 --- a/core/src/avm2/globals/flash/display/Shader.as +++ b/core/src/avm2/globals/flash/display/Shader.as @@ -4,7 +4,9 @@ package flash.display { import flash.utils.ByteArray; public class Shader { + [Ruffle(InternalSlot)] private var _data:ShaderData; + private var _precisionHint:String = ShaderPrecision.FULL; public function Shader(bytecode:ByteArray = null) { diff --git a/core/src/avm2/globals/flash/display/ShaderInput.as b/core/src/avm2/globals/flash/display/ShaderInput.as index ab199f486849..2ce1c69c514d 100644 --- a/core/src/avm2/globals/flash/display/ShaderInput.as +++ b/core/src/avm2/globals/flash/display/ShaderInput.as @@ -3,12 +3,16 @@ package flash.display { [Ruffle(InternalSlot)] private var _channels: int; + [Ruffle(InternalSlot)] private var _height: int; [Ruffle(InternalSlot)] private var _index: int; - private var _object: Object; + [Ruffle(InternalSlot)] + private var _input: Object; + + [Ruffle(InternalSlot)] private var _width: int; public function get channels():int { @@ -36,12 +40,12 @@ package flash.display { } public function get input():Object { - return _object; + return this._input; } public function set input(value:Object):void { // FIXME - validate - _object = value; + this._input = value; } } } diff --git a/core/src/avm2/globals/flash/display/ShaderJob.as b/core/src/avm2/globals/flash/display/ShaderJob.as index f66ca7c05094..630ca0ae2cdc 100644 --- a/core/src/avm2/globals/flash/display/ShaderJob.as +++ b/core/src/avm2/globals/flash/display/ShaderJob.as @@ -5,10 +5,16 @@ package flash.display { import flash.events.EventDispatcher; public class ShaderJob extends EventDispatcher { - + [Ruffle(InternalSlot)] private var _shader:Shader; + + [Ruffle(InternalSlot)] private var _target:Object; + + [Ruffle(InternalSlot)] private var _width:int; + + [Ruffle(InternalSlot)] private var _height:int; public function ShaderJob(shader:Shader = null, target:Object = null, width:int = 0, height:int = 0) { diff --git a/core/src/avm2/globals/flash/display/ShaderParameter.as b/core/src/avm2/globals/flash/display/ShaderParameter.as index 6eff29564a7c..64687e94defe 100644 --- a/core/src/avm2/globals/flash/display/ShaderParameter.as +++ b/core/src/avm2/globals/flash/display/ShaderParameter.as @@ -6,6 +6,7 @@ package flash.display { [Ruffle(InternalSlot)] private var _type:String; + [Ruffle(InternalSlot)] private var _value:Array; public function get index():int { diff --git a/core/src/avm2/globals/flash/display/bitmap.rs b/core/src/avm2/globals/flash/display/bitmap.rs index 29c0b1647062..d8bf27e0ff23 100644 --- a/core/src/avm2/globals/flash/display/bitmap.rs +++ b/core/src/avm2/globals/flash/display/bitmap.rs @@ -58,11 +58,11 @@ pub fn bitmap_allocator<'gc>( let new_bitmap_data = fill_bitmap_data_from_symbol(activation, &compressed); let bitmap_data_obj = BitmapDataObject::from_bitmap_data_internal( activation, - BitmapDataWrapper::dummy(activation.context.gc_context), + BitmapDataWrapper::dummy(activation.gc()), bitmapdata_cls, )?; - bitmap_data_obj.init_bitmap_data(activation.context.gc_context, new_bitmap_data); - new_bitmap_data.init_object2(activation.context.gc_context, bitmap_data_obj); + bitmap_data_obj.init_bitmap_data(activation.gc(), new_bitmap_data); + new_bitmap_data.init_object2(activation.gc(), bitmap_data_obj); let child = Bitmap::new_with_bitmap_data( activation.context.gc_context, @@ -72,9 +72,7 @@ pub fn bitmap_allocator<'gc>( &activation.caller_movie_or_root(), ); - let obj = initialize_for_allocator(activation, child.into(), orig_class)?; - obj.set_public_property("bitmapData", bitmap_data_obj.into(), activation)?; - return Ok(obj); + return initialize_for_allocator(activation, child.into(), orig_class); } } class_def = class.super_class(); diff --git a/core/src/avm2/globals/flash/display/bitmap_data.rs b/core/src/avm2/globals/flash/display/bitmap_data.rs index 3f80851b339b..a8c71fb4f52e 100644 --- a/core/src/avm2/globals/flash/display/bitmap_data.rs +++ b/core/src/avm2/globals/flash/display/bitmap_data.rs @@ -6,6 +6,9 @@ use crate::avm2::error::{ argument_error, make_error_2004, make_error_2007, make_error_2008, range_error, Error2004Type, }; use crate::avm2::filters::FilterAvm2Ext; +use crate::avm2::globals::slots::{ + flash_geom_point as point_slots, flash_geom_rectangle as rectangle_slots, +}; pub use crate::avm2::object::bitmap_data_allocator; use crate::avm2::object::{BitmapDataObject, ByteArrayObject, Object, TObject, VectorObject}; use crate::avm2::parameters::ParametersExt; @@ -38,16 +41,16 @@ fn get_rectangle_x_y_width_height<'gc>( rectangle: Object<'gc>, ) -> Result<(i32, i32, i32, i32), Error<'gc>> { let x = rectangle - .get_public_property("x", activation)? + .get_slot(rectangle_slots::X) .coerce_to_number(activation)?; let y = rectangle - .get_public_property("y", activation)? + .get_slot(rectangle_slots::Y) .coerce_to_number(activation)?; let width = rectangle - .get_public_property("width", activation)? + .get_slot(rectangle_slots::WIDTH) .coerce_to_number(activation)?; let height = rectangle - .get_public_property("height", activation)? + .get_slot(rectangle_slots::HEIGHT) .coerce_to_number(activation)?; let x_max = round_to_even(x + width); @@ -233,10 +236,10 @@ pub fn copy_pixels<'gc>( let dest_point = args.get_object(activation, 2, "destPoint")?; let dest_x = dest_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)?; let dest_y = dest_point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)?; if let Some(src_bitmap) = source_bitmap.as_bitmap_data() { @@ -257,10 +260,10 @@ pub fn copy_pixels<'gc>( if let Some(alpha_point) = args.try_get_object(activation, 4) { x = alpha_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)?; y = alpha_point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)?; } @@ -505,16 +508,16 @@ pub fn set_vector<'gc>( let vec = args.get_object(activation, 1, "imputVector")?; if let Some(bitmap_data) = this.as_bitmap_data() { let x = rectangle - .get_public_property("x", activation)? + .get_slot(rectangle_slots::X) .coerce_to_number(activation)?; let y = rectangle - .get_public_property("y", activation)? + .get_slot(rectangle_slots::Y) .coerce_to_number(activation)?; let width = rectangle - .get_public_property("width", activation)? + .get_slot(rectangle_slots::WIDTH) .coerce_to_number(activation)?; let height = rectangle - .get_public_property("height", activation)? + .get_slot(rectangle_slots::HEIGHT) .coerce_to_number(activation)?; // Clamp to bitmap rect. @@ -562,10 +565,10 @@ pub fn copy_channel<'gc>( let dest_point = args.get_object(activation, 2, "destPoint")?; let dest_x = dest_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)?; let dest_y = dest_point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)?; let source_channel = args.get_i32(activation, 3)?; @@ -757,10 +760,10 @@ pub fn hit_test<'gc>( let first_point = args.get_object(activation, 0, "firstPoint")?; let top_left = ( first_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)?, first_point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)?, ); let source_threshold = args.get_u32(activation, 1)?.clamp(0, u8::MAX.into()) as u8; @@ -775,11 +778,11 @@ pub fn hit_test<'gc>( if compare_object.is_of_type(point_class) { let test_point = ( compare_object - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)? - top_left.0, compare_object - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)? - top_left.1, ); @@ -792,20 +795,20 @@ pub fn hit_test<'gc>( } else if compare_object.is_of_type(rectangle_class) { let test_point = ( compare_object - .get_public_property("x", activation)? + .get_slot(rectangle_slots::X) .coerce_to_i32(activation)? - top_left.0, compare_object - .get_public_property("y", activation)? + .get_slot(rectangle_slots::Y) .coerce_to_i32(activation)? - top_left.1, ); let size = ( compare_object - .get_public_property("width", activation)? + .get_slot(rectangle_slots::WIDTH) .coerce_to_i32(activation)?, compare_object - .get_public_property("height", activation)? + .get_slot(rectangle_slots::HEIGHT) .coerce_to_i32(activation)?, ); return Ok(Value::Bool(operations::hit_test_rectangle( @@ -820,10 +823,10 @@ pub fn hit_test<'gc>( let second_point = args.get_object(activation, 3, "secondBitmapDataPoint")?; let second_point = ( second_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)?, second_point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)?, ); let second_threshold = args.get_u32(activation, 4)?.clamp(0, u8::MAX.into()) as u8; @@ -847,10 +850,10 @@ pub fn hit_test<'gc>( let second_point = args.get_object(activation, 3, "secondBitmapDataPoint")?; let second_point = ( second_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)?, second_point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)?, ); let second_threshold = args.get_u32(activation, 4)?.clamp(0, u8::MAX.into()) as u8; @@ -1113,11 +1116,10 @@ pub fn apply_filter<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(dest_bitmap) = this.as_bitmap_data() { - let source_bitmap = args.get_object(activation, 0, "sourceBitmapData")? + let source_bitmap = args + .get_object(activation, 0, "sourceBitmapData")? .as_bitmap_data() - .ok_or_else(|| { - Error::from(format!("TypeError: Error #1034: Type Coercion failed: cannot convert {} to flash.display.BitmapData.", args[0].coerce_to_string(activation).unwrap_or_default())) - })?; + .unwrap(); let source_rect = args.get_object(activation, 1, "sourceRect")?; let mut source_rect = super::display_object::object_to_rectangle(activation, source_rect)?; let filter = args.get_object(activation, 3, "filter")?; @@ -1164,10 +1166,10 @@ pub fn apply_filter<'gc>( let dest_point = args.get_object(activation, 2, "destPoint")?; let dest_point = ( dest_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_u32(activation)?, dest_point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_u32(activation)?, ); @@ -1233,10 +1235,10 @@ pub fn palette_map<'gc>( let dest_point = args.get_object(activation, 2, "destPoint")?; let dest_point = ( dest_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)?, dest_point - .get_public_property("x", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)?, ); @@ -1294,18 +1296,25 @@ pub fn perlin_noise<'gc>( let grayscale = args.get_bool(7); let offsets = args.try_get_object(activation, 8); + let point_class = activation.avm2().classes().point.inner_class_definition(); + let octave_offsets: Result, Error<'gc>> = (0..num_octaves) .map(|i| { if let Some(offsets) = offsets { if let Some(offsets) = offsets.as_array_storage() { - if let Some(Value::Object(e)) = offsets.get(i) { - let x = e - .get_public_property("x", activation)? - .coerce_to_number(activation)?; - let y = e - .get_public_property("y", activation)? - .coerce_to_number(activation)?; - Ok((x, y)) + if let Some(Value::Object(point)) = offsets.get(i) { + if point.is_of_type(point_class) { + let x = point + .get_slot(point_slots::X) + .coerce_to_number(activation)?; + let y = point + .get_slot(point_slots::Y) + .coerce_to_number(activation)?; + + Ok((x, y)) + } else { + Ok((0.0, 0.0)) + } } else { Ok((0.0, 0.0)) } @@ -1350,10 +1359,10 @@ pub fn threshold<'gc>( let dest_point = args.get_object(activation, 2, "destPoint")?; let dest_point = ( dest_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)?, dest_point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)?, ); let operation = args.try_get_string(activation, 3)?; @@ -1493,10 +1502,10 @@ pub fn pixel_dissolve<'gc>( let dest_point = args.get_object(activation, 2, "destPoint")?; let dest_point = ( dest_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)?, dest_point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)?, ); @@ -1554,11 +1563,11 @@ pub fn merge<'gc>( let dest_point = args.get_object(activation, 2, "destPoint")?; let x = dest_point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_i32(activation)?; let y = dest_point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_i32(activation)?; (x, y) diff --git a/core/src/avm2/globals/flash/display/display_object.rs b/core/src/avm2/globals/flash/display/display_object.rs index 92f8737b269e..1c2a82bb8cfc 100644 --- a/core/src/avm2/globals/flash/display/display_object.rs +++ b/core/src/avm2/globals/flash/display/display_object.rs @@ -3,6 +3,11 @@ use crate::avm2::activation::Activation; use crate::avm2::error::{illegal_operation_error, make_error_2007, make_error_2008}; use crate::avm2::filters::FilterAvm2Ext; +use crate::avm2::globals::flash::geom::transform::color_transform_from_transform_object; +use crate::avm2::globals::flash::geom::transform::matrix_from_transform_object; +use crate::avm2::globals::slots::flash_display_shader as shader_slots; +use crate::avm2::globals::slots::flash_geom_point as point_slots; +use crate::avm2::globals::slots::flash_geom_rectangle as rectangle_slots; use crate::avm2::object::{Object, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; @@ -751,28 +756,9 @@ pub fn set_transform<'gc>( let transform = args.get_object(activation, 0, "transform")?; // FIXME - consider 3D matrix and pixel bounds - let matrix = transform - .get_public_property("matrix", activation)? - .as_object(); - - let Some(matrix) = matrix else { - // FP seems to not do anything when setting to a Transform with a null matrix, - // but we don't actually support setting the matrix to null anyway - // (see the comment in `flash::geom::transform::set_matrix`) - return Ok(Value::Undefined); - }; - - let color_transform = transform - .get_public_property("colorTransform", activation)? - .as_object() - .expect("colorTransform should be non-null"); - - let matrix = - crate::avm2::globals::flash::geom::transform::object_to_matrix(matrix, activation)?; - let color_transform = crate::avm2::globals::flash::geom::transform::object_to_color_transform( - color_transform, - activation, - )?; + let matrix = matrix_from_transform_object(transform); + + let color_transform = color_transform_from_transform_object(transform); let dobj = this.as_display_object().unwrap(); let mut write = dobj.base_mut(activation.context.gc_context); @@ -856,13 +842,18 @@ pub fn object_to_rectangle<'gc>( activation: &mut Activation<'_, 'gc>, object: Object<'gc>, ) -> Result, Error<'gc>> { - const NAMES: &[&str] = &["x", "y", "width", "height"]; + const SLOTS: &[u32] = &[ + rectangle_slots::X, + rectangle_slots::Y, + rectangle_slots::WIDTH, + rectangle_slots::HEIGHT, + ]; + let mut values = [0.0; 4]; - for (&name, value) in NAMES.iter().zip(&mut values) { - *value = object - .get_public_property(name, activation)? - .coerce_to_number(activation)?; + for (slot, value) in SLOTS.iter().zip(&mut values) { + *value = object.get_slot(*slot).coerce_to_number(activation)?; } + let [x, y, width, height] = values; Ok(Rectangle { x_min: Twips::from_pixels_i32(round_to_even(x)), @@ -908,10 +899,10 @@ pub fn local_to_global<'gc>( if let Some(dobj) = this.as_display_object() { let point = args.get_object(activation, 0, "point")?; let x = point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_number(activation)?; let y = point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_number(activation)?; let local = Point::from_pixels(x, y); @@ -938,10 +929,10 @@ pub fn global_to_local<'gc>( if let Some(dobj) = this.as_display_object() { let point = args.get_object(activation, 0, "point")?; let x = point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_number(activation)?; let y = point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_number(activation)?; let global = Point::from_pixels(x, y); @@ -1100,7 +1091,7 @@ pub fn set_blend_shader<'gc>( if let Some(dobj) = this.as_display_object() { let Some(shader_data) = args .get_object(activation, 0, "shader")? - .get_public_property("data", activation)? + .get_slot(shader_slots::_DATA) .as_object() else { return Err(make_error_2007(activation, "data")); diff --git a/core/src/avm2/globals/flash/display/display_object_container.rs b/core/src/avm2/globals/flash/display/display_object_container.rs index 5bc49c68c60e..5b0461eef44d 100644 --- a/core/src/avm2/globals/flash/display/display_object_container.rs +++ b/core/src/avm2/globals/flash/display/display_object_container.rs @@ -5,6 +5,7 @@ use swf::Twips; use crate::avm2::activation::Activation; use crate::avm2::error::{argument_error, make_error_2025, range_error}; +use crate::avm2::globals::slots::flash_geom_point as point_slots; use crate::avm2::object::{Object, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; @@ -29,7 +30,7 @@ fn validate_add_operation<'gc>( ) -> Result<(), Error<'gc>> { let ctr = new_parent .as_container() - .ok_or("ArgumentError: Parent is not a DisplayObjectContainer")?; + .expect("Parent must be a DisplayObjectContainer"); if let DisplayObject::Stage(_) = proposed_child { return Err(Error::AvmError(argument_error( @@ -93,7 +94,7 @@ fn validate_remove_operation<'gc>( ) -> Result<(), Error<'gc>> { let old_ctr = old_parent .as_container() - .ok_or("ArgumentError: Parent is not a DisplayObjectContainer")?; + .expect("Parent must be a DisplayObjectContainer"); for child in old_ctr.iter_render_list() { if DisplayObject::ptr_eq(child, proposed_child) { @@ -185,7 +186,7 @@ pub fn add_child<'gc>( let child = args .get_object(activation, 0, "child")? .as_display_object() - .ok_or("ArgumentError: Child not a valid display object")?; + .expect("Child must be a display object"); let target_index = ctr.num_children(); @@ -209,7 +210,7 @@ pub fn add_child_at<'gc>( let child = args .get_object(activation, 0, "child")? .as_display_object() - .ok_or("ArgumentError: Child not a valid display object")?; + .expect("Child must be a display object"); let target_index = args.get_u32(activation, 1)? as usize; validate_add_operation(activation, parent, child, target_index)?; @@ -231,7 +232,7 @@ pub fn remove_child<'gc>( let child = args .get_object(activation, 0, "child")? .as_display_object() - .ok_or("ArgumentError: Child not a valid display object")?; + .expect("Child must be a display object"); validate_remove_operation(activation, parent, child)?; remove_child_from_displaylist(activation.context, child); @@ -410,7 +411,7 @@ pub fn set_child_index<'gc>( let child = args .get_object(activation, 0, "child")? .as_display_object() - .ok_or("ArgumentError: Child not a valid display object")?; + .expect("Child must be a display object"); let target_index = args.get_u32(activation, 1)? as usize; let child_parent = child.parent(); @@ -481,11 +482,11 @@ pub fn swap_children<'gc>( let child0 = args .get_object(activation, 0, "child")? .as_display_object() - .ok_or("ArgumentError: Child is not a display object")?; + .expect("Child must be a display object"); let child1 = args .get_object(activation, 1, "child")? .as_display_object() - .ok_or("ArgumentError: Child is not a display object")?; + .expect("Child must be a display object"); let index0 = ctr .iter_render_list() @@ -547,10 +548,10 @@ pub fn get_objects_under_point<'gc>( let point = args.get_object(activation, 0, "point")?; let x = point - .get_public_property("x", activation)? + .get_slot(point_slots::X) .coerce_to_number(activation)?; let y = point - .get_public_property("y", activation)? + .get_slot(point_slots::Y) .coerce_to_number(activation)?; let point = Point { diff --git a/core/src/avm2/globals/flash/display/graphics.rs b/core/src/avm2/globals/flash/display/graphics.rs index 8867675f94fb..0e66266448a5 100644 --- a/core/src/avm2/globals/flash/display/graphics.rs +++ b/core/src/avm2/globals/flash/display/graphics.rs @@ -6,6 +6,12 @@ use crate::avm2::activation::Activation; use crate::avm2::error::{make_error_2004, make_error_2007, make_error_2008, Error2004Type}; use crate::avm2::globals::flash::geom::transform::object_to_matrix; +use crate::avm2::globals::slots::flash_display_graphics_bitmap_fill as graphics_bitmap_fill_slots; +use crate::avm2::globals::slots::flash_display_graphics_gradient_fill as graphics_gradient_fill_slots; +use crate::avm2::globals::slots::flash_display_graphics_path as graphics_path_slots; +use crate::avm2::globals::slots::flash_display_graphics_solid_fill as graphics_solid_fill_slots; +use crate::avm2::globals::slots::flash_display_graphics_stroke as graphics_stroke_slots; +use crate::avm2::globals::slots::flash_display_graphics_triangle_path as graphics_triangle_path_slots; use crate::avm2::object::{Object, TObject, VectorObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; @@ -66,8 +72,7 @@ pub fn begin_bitmap_fill<'gc>( let is_repeating = args.get_bool(2); let is_smoothed = args.get_bool(3); - let handle = - bitmap.bitmap_handle(activation.context.gc_context, activation.context.renderer); + let handle = bitmap.bitmap_handle(activation.gc(), activation.context.renderer); let bitmap = ruffle_render::bitmap::BitmapInfo { handle, @@ -79,7 +84,7 @@ pub fn begin_bitmap_fill<'gc>( (Twips::TWIPS_PER_PIXEL as i16).into(), ); - if let Some(mut draw) = this.as_drawing(activation.context.gc_context) { + if let Some(mut draw) = this.as_drawing(activation.gc()) { let id = draw.add_bitmap(bitmap); draw.set_fill_style(Some(FillStyle::Bitmap { id, @@ -975,6 +980,14 @@ pub fn draw_path<'gc>( let data = args.get_object(activation, 1, "data")?; let winding = args.get_string(activation, 2)?; + let fill_rule = if winding == WStr::from_units(b"nonZero") { + FillRule::NonZero + } else if winding == WStr::from_units(b"evenOdd") { + FillRule::EvenOdd + } else { + return Err(make_error_2008(activation, "winding")); + }; + // FIXME - implement fill behavior described in the Flash docs // (which is different from just running each command sequentially on `Graphics`) avm2_stub_method!( @@ -989,7 +1002,7 @@ pub fn draw_path<'gc>( .expect("commands is not a Vector"); let data = data.as_vector_storage().expect("data is not a Vector"); - process_commands(activation, &mut drawing, &commands, &data, winding)?; + process_commands(activation, &mut drawing, &commands, &data, fill_rule)?; Ok(Value::Undefined) } @@ -1326,7 +1339,7 @@ fn process_commands<'gc>( drawing: &mut Drawing, commands: &VectorStorage<'gc>, data: &VectorStorage<'gc>, - winding: AvmString, + fill_rule: FillRule, ) -> Result<(), Error<'gc>> { // Flash special cases this, and doesn't throw an error, // even if data has odd number of coordinates. @@ -1340,15 +1353,7 @@ fn process_commands<'gc>( return Err(make_error_2004(activation, Error2004Type::ArgumentError)); } - let rule = if winding == WStr::from_units(b"nonZero") { - FillRule::NonZero - } else if winding == WStr::from_units(b"evenOdd") { - FillRule::EvenOdd - } else { - return Err(make_error_2008(activation, "winding")); - }; - - drawing.set_fill_rule(Some(rule)); + drawing.set_fill_rule(Some(fill_rule)); fn process_command<'gc>( activation: &mut Activation<'_, 'gc>, @@ -1442,21 +1447,29 @@ fn handle_igraphics_data<'gc>( let style = handle_gradient_fill(activation, obj)?; drawing.set_fill_style(Some(style)); } else if class == activation.avm2().class_defs().graphicspath { - let commands = obj.get_public_property("commands", activation)?.as_object(); + let commands = obj.get_slot(graphics_path_slots::COMMANDS).as_object(); - let data = obj.get_public_property("data", activation)?.as_object(); + let data = obj.get_slot(graphics_path_slots::DATA).as_object(); let winding = obj - .get_public_property("winding", activation)? + .get_slot(graphics_path_slots::_WINDING) .coerce_to_string(activation)?; + let fill_rule = if winding == WStr::from_units(b"nonZero") { + FillRule::NonZero + } else if winding == WStr::from_units(b"evenOdd") { + FillRule::EvenOdd + } else { + unreachable!("AS3 setter guarantees value of winding"); + }; + if let (Some(commands), Some(data)) = (commands, data) { process_commands( activation, drawing, &commands.as_vector_storage().unwrap(), &data.as_vector_storage().unwrap(), - winding, + fill_rule, )?; } } else if class == activation.avm2().class_defs().graphicssolidfill { @@ -1467,7 +1480,7 @@ fn handle_igraphics_data<'gc>( drawing.set_fill_style(None); } else if class == activation.avm2().class_defs().graphicsstroke { let thickness = obj - .get_public_property("thickness", activation)? + .get_slot(graphics_stroke_slots::THICKNESS) .coerce_to_number(activation)?; if thickness.is_nan() { @@ -1475,12 +1488,12 @@ fn handle_igraphics_data<'gc>( } else { let caps = { let caps = obj - .get_public_property("caps", activation)? + .get_slot(graphics_stroke_slots::CAPS) .coerce_to_string(activation); caps_to_cap_style(caps.ok()) }; let fill = { - let fill = obj.get_public_property("fill", activation)?.as_object(); + let fill = obj.get_slot(graphics_stroke_slots::FILL).as_object(); if let Some(fill) = fill { handle_igraphics_fill(activation, drawing, &fill)? @@ -1490,17 +1503,17 @@ fn handle_igraphics_data<'gc>( }; let joints = obj - .get_public_property("joints", activation)? + .get_slot(graphics_stroke_slots::JOINTS) .coerce_to_string(activation) .ok(); let miter_limit = obj - .get_public_property("miterLimit", activation)? + .get_slot(graphics_stroke_slots::MITER_LIMIT) .coerce_to_number(activation)?; let pixel_hinting = obj - .get_public_property("pixelHinting", activation)? + .get_slot(graphics_stroke_slots::PIXEL_HINTING) .coerce_to_boolean(); let scale_mode = obj - .get_public_property("scaleMode", activation)? + .get_slot(graphics_stroke_slots::SCALE_MODE) .coerce_to_string(activation)?; let width = Twips::from_pixels(thickness.clamp(0.0, 255.0)); @@ -1539,16 +1552,22 @@ fn handle_graphics_triangle_path<'gc>( ) -> Result<(), Error<'gc>> { let culling = { let culling = obj - .get_public_property("culling", activation)? + .get_slot(graphics_triangle_path_slots::CULLING) .coerce_to_string(activation)?; TriangleCulling::from_string(culling) .ok_or_else(|| make_error_2008(activation, "culling"))? }; - let vertices = obj.get_public_property("vertices", activation)?.as_object(); - let indices = obj.get_public_property("indices", activation)?.as_object(); - let uvt_data = obj.get_public_property("uvtData", activation)?.as_object(); + let vertices = obj + .get_slot(graphics_triangle_path_slots::VERTICES) + .as_object(); + let indices = obj + .get_slot(graphics_triangle_path_slots::INDICES) + .as_object(); + let uvt_data = obj + .get_slot(graphics_triangle_path_slots::UVT_DATA) + .as_object(); if let Some(vertices) = vertices { draw_triangles_internal( @@ -1596,12 +1615,12 @@ fn handle_solid_fill<'gc>( obj: &Object<'gc>, ) -> Result> { let alpha = obj - .get_public_property("alpha", activation)? + .get_slot(graphics_solid_fill_slots::ALPHA) .coerce_to_number(activation) .unwrap_or(1.0); let color = obj - .get_public_property("color", activation)? + .get_slot(graphics_solid_fill_slots::COLOR) .coerce_to_u32(activation) .unwrap_or(0); @@ -1612,18 +1631,23 @@ fn handle_gradient_fill<'gc>( activation: &mut Activation<'_, 'gc>, obj: &Object<'gc>, ) -> Result> { - let alphas = obj.get_public_property("alphas", activation)?.as_object(); - let ratios = obj.get_public_property("ratios", activation)?.as_object(); + let alphas = obj + .get_slot(graphics_gradient_fill_slots::ALPHAS) + .as_object(); + let ratios = obj + .get_slot(graphics_gradient_fill_slots::RATIOS) + .as_object(); let colors = obj - .get_public_property("colors", activation)? + .get_slot(graphics_gradient_fill_slots::COLORS) .as_object() .ok_or_else(|| make_error_2007(activation, "colors"))?; let gradient_type = { let gradient_type = obj - .get_public_property("type", activation)? + .get_slot(graphics_gradient_fill_slots::TYPE) .coerce_to_string(activation)?; + parse_gradient_type(activation, gradient_type)? }; @@ -1635,7 +1659,9 @@ fn handle_gradient_fill<'gc>( )?; let matrix = { - let matrix = obj.get_public_property("matrix", activation)?.as_object(); + let matrix = obj + .get_slot(graphics_gradient_fill_slots::MATRIX) + .as_object(); match matrix { Some(matrix) => Matrix::from(object_to_matrix(matrix, activation)?), @@ -1645,7 +1671,7 @@ fn handle_gradient_fill<'gc>( let spread = { let spread_method = obj - .get_public_property("spreadMethod", activation)? + .get_slot(graphics_gradient_fill_slots::SPREAD_METHOD) .coerce_to_string(activation)?; parse_spread_method(spread_method) @@ -1653,14 +1679,14 @@ fn handle_gradient_fill<'gc>( let interpolation = { let interpolation_method = obj - .get_public_property("interpolationMethod", activation)? + .get_slot(graphics_gradient_fill_slots::INTERPOLATION_METHOD) .coerce_to_string(activation)?; parse_interpolation_method(interpolation_method) }; let focal_point = obj - .get_public_property("focalPointRatio", activation)? + .get_slot(graphics_gradient_fill_slots::FOCAL_POINT_RATIO) .coerce_to_number(activation)?; let fill = match gradient_type { @@ -1696,14 +1722,14 @@ fn handle_bitmap_fill<'gc>( obj: &Object<'gc>, ) -> Result> { let bitmap_data = obj - .get_public_property("bitmapData", activation)? + .get_slot(graphics_bitmap_fill_slots::BITMAP_DATA) .as_object() .ok_or_else(|| make_error_2007(activation, "bitmap"))? .as_bitmap_data() .expect("Bitmap argument is ensured to be a BitmapData from actionscript"); let matrix = obj - .get_public_property("matrix", activation)? + .get_slot(graphics_bitmap_fill_slots::MATRIX) .as_object() .and_then(|matrix| { let matrix = Matrix::from(object_to_matrix(matrix, activation).ok()?); @@ -1713,11 +1739,11 @@ fn handle_bitmap_fill<'gc>( .unwrap_or(Matrix::IDENTITY); let is_repeating = obj - .get_public_property("repeat", activation)? + .get_slot(graphics_bitmap_fill_slots::REPEAT) .coerce_to_boolean(); let is_smoothed = obj - .get_public_property("smooth", activation)? + .get_slot(graphics_bitmap_fill_slots::SMOOTH) .coerce_to_boolean(); let handle = diff --git a/core/src/avm2/globals/flash/display/loader.rs b/core/src/avm2/globals/flash/display/loader.rs index 8a619053da2d..b69f08c75618 100644 --- a/core/src/avm2/globals/flash/display/loader.rs +++ b/core/src/avm2/globals/flash/display/loader.rs @@ -5,7 +5,8 @@ use indexmap::IndexMap; use crate::avm2::activation::Activation; use crate::avm2::error::make_error_2007; use crate::avm2::globals::flash::display::display_object::initialize_for_allocator; -use crate::avm2::globals::slots::flash_display_loader as slots; +use crate::avm2::globals::slots::flash_display_loader as loader_slots; +use crate::avm2::globals::slots::flash_net_url_request as url_request_slots; use crate::avm2::object::LoaderInfoObject; use crate::avm2::object::LoaderStream; use crate::avm2::object::TObject; @@ -45,7 +46,11 @@ pub fn loader_allocator<'gc>( None, false, )?; - loader.set_slot(slots::_CONTENT_LOADER_INFO, loader_info.into(), activation)?; + loader.set_slot( + loader_slots::_CONTENT_LOADER_INFO, + loader_info.into(), + activation, + )?; Ok(loader) } @@ -58,13 +63,13 @@ pub fn load<'gc>( let context = args.try_get_object(activation, 1); let loader_info = this - .get_slot(slots::_CONTENT_LOADER_INFO) + .get_slot(loader_slots::_CONTENT_LOADER_INFO) .as_object() .unwrap(); - let loader_info_object = loader_info.as_loader_info_object().unwrap(); + let loader_info = loader_info.as_loader_info_object().unwrap(); - if loader_info_object.init_event_fired() { + if loader_info.init_event_fired() { // FIXME: When calling load/loadBytes, then calling load/loadBytes again // before the `init` event is fired, the first load is cancelled. avm2_stub_method!( @@ -76,7 +81,7 @@ pub fn load<'gc>( } // Unload the loader, in case something was already loaded. - loader_info_object.unload(activation); + loader_info.unload(activation); // This is a dummy MovieClip, which will get overwritten in `Loader` let content = MovieClip::new( @@ -85,17 +90,14 @@ pub fn load<'gc>( ); // Update the LoaderStream - we still have a fake SwfMovie, but we now have the real target clip. - loader_info - .as_loader_info_object() - .unwrap() - .set_loader_stream( - LoaderStream::NotYetLoaded( - Arc::new(SwfMovie::empty(activation.context.swf.version())), - Some(content.into()), - false, - ), - activation.context.gc_context, - ); + loader_info.set_loader_stream( + LoaderStream::NotYetLoaded( + Arc::new(SwfMovie::empty(activation.context.swf.version())), + Some(content.into()), + false, + ), + activation.context.gc_context, + ); let request = request_from_url_request(activation, url_request)?; @@ -106,7 +108,7 @@ pub fn load<'gc>( request, Some(url), MovieLoaderVMData::Avm2 { - loader_info, + loader_info: *loader_info, context, default_domain: activation .caller_domain() @@ -125,17 +127,17 @@ pub fn request_from_url_request<'gc>( // FIXME: set `followRedirects` and `userAgent` // from the `URLRequest` - let mut url = match url_request.get_public_property("url", activation)? { + let mut url = match url_request.get_slot(url_request_slots::_URL) { Value::Null => return Err(make_error_2007(activation, "url")), url => url.coerce_to_string(activation)?.to_string(), }; let method = url_request - .get_public_property("method", activation)? + .get_slot(url_request_slots::_METHOD) .coerce_to_string(activation)?; let headers = url_request - .get_public_property("requestHeaders", activation)? + .get_slot(url_request_slots::_REQUEST_HEADERS) .as_object(); let mut string_headers = IndexMap::default(); @@ -167,7 +169,7 @@ pub fn request_from_url_request<'gc>( let method = NavigationMethod::from_method_str(&method).expect("URLRequest should have a valid method"); - let data = url_request.get_public_property("data", activation)?; + let data = url_request.get_slot(url_request_slots::_DATA); let body = match (method, data) { (_, Value::Null | Value::Undefined) => None, (NavigationMethod::Get, data) => { @@ -184,7 +186,7 @@ pub fn request_from_url_request<'gc>( } (NavigationMethod::Post, data) => { let content_type = url_request - .get_public_property("contentType", activation)? + .get_slot(url_request_slots::_CONTENT_TYPE) .coerce_to_string(activation)? .to_string(); if let Some(ba) = data.as_object().and_then(|o| o.as_bytearray_object()) { @@ -218,13 +220,13 @@ pub fn load_bytes<'gc>( let context = args.try_get_object(activation, 1); let loader_info = this - .get_slot(slots::_CONTENT_LOADER_INFO) + .get_slot(loader_slots::_CONTENT_LOADER_INFO) .as_object() .unwrap(); - let loader_info_object = loader_info.as_loader_info_object().unwrap(); + let loader_info = loader_info.as_loader_info_object().unwrap(); - if loader_info_object.init_event_fired() { + if loader_info.init_event_fired() { // FIXME: When calling load/loadBytes, then calling load/loadBytes again // before the `init` event is fired, the first load is cancelled. avm2_stub_method!( @@ -236,7 +238,7 @@ pub fn load_bytes<'gc>( } // Unload the loader, in case something was already loaded. - loader_info_object.unload(activation); + loader_info.unload(activation); // This is a dummy MovieClip, which will get overwritten in `Loader` let content = MovieClip::new( @@ -253,7 +255,7 @@ pub fn load_bytes<'gc>( content.into(), bytes, MovieLoaderVMData::Avm2 { - loader_info, + loader_info: *loader_info, context, default_domain, }, @@ -275,13 +277,13 @@ pub fn unload<'gc>( avm2_stub_method!(activation, "flash.display.Loader", "unload"); let loader_info = this - .get_slot(slots::_CONTENT_LOADER_INFO) + .get_slot(loader_slots::_CONTENT_LOADER_INFO) .as_object() .unwrap(); - let loader_info_object = loader_info.as_loader_info_object().unwrap(); + let loader_info = loader_info.as_loader_info_object().unwrap(); - loader_info_object.unload(activation); + loader_info.unload(activation); Ok(Value::Undefined) } diff --git a/core/src/avm2/globals/flash/display/loader_info.rs b/core/src/avm2/globals/flash/display/loader_info.rs index 612e928c5263..79123b1a9586 100644 --- a/core/src/avm2/globals/flash/display/loader_info.rs +++ b/core/src/avm2/globals/flash/display/loader_info.rs @@ -3,7 +3,7 @@ use crate::avm2::activation::Activation; use crate::avm2::bytearray::Endian; use crate::avm2::error::error; -use crate::avm2::object::{DomainObject, LoaderStream, Object, TObject}; +use crate::avm2::object::{DomainObject, LoaderStream, Object, ScriptObject, TObject}; use crate::avm2::value::Value; use crate::avm2::{AvmString, Error}; use crate::display_object::TDisplayObject; @@ -506,17 +506,13 @@ pub fn get_parameters<'gc>( LoaderStream::Swf(root, _) => root, }; - let params_obj = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; + let params_obj = ScriptObject::new_object(activation); let parameters = root.parameters(); for (k, v) in parameters.iter() { let avm_k = AvmString::new_utf8(activation.context.gc_context, k); let avm_v = AvmString::new_utf8(activation.context.gc_context, v); - params_obj.set_public_property(avm_k, avm_v.into(), activation)?; + params_obj.set_string_property_local(avm_k, avm_v.into(), activation)?; } return Ok(params_obj.into()); diff --git a/core/src/avm2/globals/flash/display/shader_data.rs b/core/src/avm2/globals/flash/display/shader_data.rs index 40e5c2f98b09..33c2ef560cd6 100644 --- a/core/src/avm2/globals/flash/display/shader_data.rs +++ b/core/src/avm2/globals/flash/display/shader_data.rs @@ -28,9 +28,9 @@ pub fn init<'gc>( // Top-level metadata appears to turn `TInt` into a plain integer value, // rather than a single-element array. let value = meta.value.as_avm2_value(activation, true)?; - this.set_public_property(name, value, activation)?; + this.set_string_property_local(name, value, activation)?; } - this.set_public_property( + this.set_string_property_local( "name", AvmString::new_utf8(activation.context.gc_context, &shader.name).into(), activation, @@ -62,7 +62,7 @@ pub fn init<'gc>( let name = AvmString::new_utf8(activation.context.gc_context, name); let param_obj = make_shader_parameter(activation, param, index)?; - this.set_public_property(name, param_obj, activation)?; + this.set_string_property_local(name, param_obj, activation)?; } let shader_handle = activation diff --git a/core/src/avm2/globals/flash/display/shader_job.rs b/core/src/avm2/globals/flash/display/shader_job.rs index da360c9ef062..e801c93132f6 100644 --- a/core/src/avm2/globals/flash/display/shader_job.rs +++ b/core/src/avm2/globals/flash/display/shader_job.rs @@ -1,19 +1,21 @@ -use ruffle_render::{ - backend::{PixelBenderOutput, PixelBenderTarget}, - bitmap::PixelRegion, - pixel_bender::{ - ImageInputTexture, PixelBenderParam, PixelBenderParamQualifier, PixelBenderShaderArgument, - PixelBenderShaderHandle, PixelBenderType, OUT_COORD_NAME, - }, +use crate::avm2::bytearray::Endian; +use crate::avm2::globals::slots::{ + flash_display_shader as shader_slots, flash_display_shader_input as shader_input_slots, + flash_display_shader_job as shader_job_slots, + flash_display_shader_parameter as shader_parameter_slots, }; +use crate::avm2::parameters::ParametersExt; +use crate::avm2::{Activation, Error, Object, TObject, Value}; +use crate::pixel_bender::PixelBenderTypeExt; +use crate::string::AvmString; -use crate::{ - avm2::{ - bytearray::Endian, parameters::ParametersExt, string::AvmString, Activation, Error, Object, - TObject, Value, - }, - avm2_stub_method, - pixel_bender::PixelBenderTypeExt, +use crate::avm2_stub_method; + +use ruffle_render::backend::{PixelBenderOutput, PixelBenderTarget}; +use ruffle_render::bitmap::PixelRegion; +use ruffle_render::pixel_bender::{ + ImageInputTexture, PixelBenderParam, PixelBenderParamQualifier, PixelBenderShaderArgument, + PixelBenderShaderHandle, PixelBenderType, OUT_COORD_NAME, }; pub fn get_shader_args<'gc>( @@ -29,7 +31,7 @@ pub fn get_shader_args<'gc>( // FIXME - determine what errors Flash Player throws here // instead of using `expect` let shader_data = shader_obj - .get_public_property("data", activation)? + .get_slot(shader_slots::_DATA) .as_object() .expect("Missing ShaderData object") .as_shader_data() @@ -65,7 +67,7 @@ pub fn get_shader_args<'gc>( }); } let shader_param = shader_data - .get_public_property( + .get_string_property_local( AvmString::new_utf8(activation.context.gc_context, name), activation, ) @@ -75,9 +77,18 @@ pub fn get_shader_args<'gc>( .as_object() .expect("Shader property is not an object"); - let value = shader_param - .get_public_property("value", activation) - .expect("Missing value property"); + if !shader_param.is_of_type( + activation + .avm2() + .classes() + .shaderparameter + .inner_class_definition(), + ) { + panic!("Expected shader parameter to be of class ShaderParameter"); + } + + let value = shader_param.get_slot(shader_parameter_slots::_VALUE); + let pb_val = PixelBenderType::from_avm2_value(activation, value, param_type) .expect("Failed to convert AVM2 value to PixelBenderType"); @@ -92,7 +103,7 @@ pub fn get_shader_args<'gc>( name, } => { let shader_input = shader_data - .get_public_property( + .get_string_property_local( AvmString::new_utf8(activation.context.gc_context, name), activation, ) @@ -100,33 +111,28 @@ pub fn get_shader_args<'gc>( .as_object() .expect("Shader input is not an object"); - let input = shader_input - .get_public_property("input", activation) - .expect("Missing input property"); + if !shader_input.is_of_type( + activation + .avm2() + .classes() + .shaderinput + .inner_class_definition(), + ) { + panic!("Expected shader input to be of class ShaderInput"); + } - let width = shader_input - .get_public_property("width", activation) - .unwrap() - .as_u32(); - let height = shader_input - .get_public_property("height", activation) - .unwrap() - .as_u32(); + let input = shader_input.get_slot(shader_input_slots::_INPUT); + + let width = shader_input.get_slot(shader_input_slots::_WIDTH).as_u32(); + let height = shader_input.get_slot(shader_input_slots::_HEIGHT).as_u32(); let input_channels = shader_input - .get_public_property("channels", activation) - .unwrap() + .get_slot(shader_input_slots::_CHANNELS) .as_u32(); assert_eq!(*channels as u32, input_channels); - let texture = if let Value::Null = input { - None - } else { - let input = input - .as_object() - .expect("ShaderInput.input is not an object"); - + let texture = if let Some(input) = input.as_object() { let input_texture = if let Some(bitmap) = input.as_bitmap_data() { ImageInputTexture::Bitmap(bitmap.bitmap_handle( activation.context.gc_context, @@ -159,6 +165,9 @@ pub fn get_shader_args<'gc>( panic!("Unexpected input object {input:?}"); }; Some(input_texture) + } else { + // Null input + None }; Some(PixelBenderShaderArgument::ImageInput { @@ -190,20 +199,20 @@ pub fn start<'gc>( ); } let shader = this - .get_public_property("shader", activation)? + .get_slot(shader_job_slots::_SHADER) .as_object() .expect("Missing Shader object"); let (shader_handle, arguments) = get_shader_args(shader, activation)?; let target = this - .get_public_property("target", activation)? + .get_slot(shader_job_slots::_TARGET) .as_object() .expect("ShaderJob.target is not an object"); - let output_width = this.get_public_property("width", activation)?.as_u32(); + let output_width = this.get_slot(shader_job_slots::_WIDTH).as_u32(); - let output_height = this.get_public_property("height", activation)?.as_u32(); + let output_height = this.get_slot(shader_job_slots::_HEIGHT).as_u32(); let pixel_bender_target = if let Some(bitmap) = target.as_bitmap_data() { let target_bitmap = bitmap.sync(activation.context.renderer); diff --git a/core/src/avm2/globals/flash/display/shader_parameter.rs b/core/src/avm2/globals/flash/display/shader_parameter.rs index bc6513f5d1e9..9c17b4919b4c 100644 --- a/core/src/avm2/globals/flash/display/shader_parameter.rs +++ b/core/src/avm2/globals/flash/display/shader_parameter.rs @@ -35,10 +35,10 @@ pub fn make_shader_parameter<'gc>( obj.set_public_property(name, value, activation)?; if &*name == b"defaultValue" { - obj.set_public_property("value", value, activation)?; + obj.set_slot(parameter_slots::_VALUE, value, activation)?; } } - obj.set_public_property( + obj.set_string_property_local( "name", AvmString::new_utf8(activation.context.gc_context, name).into(), activation, @@ -53,7 +53,7 @@ pub fn make_shader_parameter<'gc>( .construct(activation, &[])?; obj.set_slot(input_slots::_CHANNELS, (*channels).into(), activation)?; obj.set_slot(input_slots::_INDEX, index.into(), activation)?; - obj.set_public_property( + obj.set_string_property_local( "name", AvmString::new_utf8(activation.context.gc_context, name).into(), activation, diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index 507850398ae6..a1736a4c6b93 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -2,7 +2,9 @@ use crate::avm2::activation::Activation; use crate::avm2::globals::flash::display::display_object::initialize_for_allocator; -use crate::avm2::globals::slots::flash_display_sprite as slots; +use crate::avm2::globals::slots::{ + flash_display_sprite as sprite_slots, flash_geom_rectangle as rectangle_slots, +}; use crate::avm2::object::{Object, StageObject, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; @@ -69,10 +71,10 @@ pub fn get_graphics<'gc>( ) -> Result, Error<'gc>> { if let Some(dobj) = this.as_display_object() { // Lazily initialize the `Graphics` object in a hidden property. - let graphics = match this.get_slot(slots::_GRAPHICS) { + let graphics = match this.get_slot(sprite_slots::_GRAPHICS) { Value::Undefined | Value::Null => { let graphics = Value::from(StageObject::graphics(activation, dobj)?); - this.set_slot(slots::_GRAPHICS, graphics, activation)?; + this.set_slot(sprite_slots::_GRAPHICS, graphics, activation)?; graphics } graphics => graphics, @@ -105,8 +107,8 @@ pub fn set_sound_transform<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(dobj) = this.as_display_object() { - let as3_st = args.get_object(activation, 0, "value")?; - let dobj_st = SoundTransform::from_avm2_object(activation, as3_st)?; + let as3_st = args.get_object(activation, 0, "soundTransform")?; + let dobj_st = SoundTransform::from_avm2_object(as3_st); dobj.set_sound_transform(activation.context, dobj_st); } @@ -154,19 +156,19 @@ pub fn start_drag<'gc>( let rectangle = args.try_get_object(activation, 1); let constraint = if let Some(rectangle) = rectangle { let x = rectangle - .get_public_property("x", activation)? + .get_slot(rectangle_slots::X) .coerce_to_number(activation)?; let y = rectangle - .get_public_property("y", activation)? + .get_slot(rectangle_slots::Y) .coerce_to_number(activation)?; let width = rectangle - .get_public_property("width", activation)? + .get_slot(rectangle_slots::WIDTH) .coerce_to_number(activation)?; let height = rectangle - .get_public_property("height", activation)? + .get_slot(rectangle_slots::HEIGHT) .coerce_to_number(activation)?; // Normalize the bounds. diff --git a/core/src/avm2/globals/flash/display3D/context_3d.rs b/core/src/avm2/globals/flash/display3D/context_3d.rs index 19ba9cc40d6e..cfdaa090d2a7 100644 --- a/core/src/avm2/globals/flash/display3D/context_3d.rs +++ b/core/src/avm2/globals/flash/display3D/context_3d.rs @@ -1,4 +1,6 @@ use crate::avm2::error::{argument_error, error, make_error_2008}; +use crate::avm2::globals::slots::flash_geom_matrix_3d as matrix3d_slots; +use crate::avm2::globals::slots::flash_geom_rectangle as rectangle_slots; use crate::avm2::parameters::ParametersExt; use crate::avm2::Activation; use crate::avm2::TObject; @@ -305,7 +307,7 @@ pub fn set_program_constants_from_matrix<'gc>( } let matrix_raw_data = matrix - .get_public_property("rawData", activation)? + .get_slot(matrix3d_slots::_RAW_DATA) .as_object() .expect("rawData cannot be null"); @@ -711,16 +713,16 @@ pub fn set_scissor_rectangle<'gc>( let rectangle = args.try_get_object(activation, 0); let rectangle = if let Some(rectangle) = rectangle { let x = rectangle - .get_public_property("x", activation)? + .get_slot(rectangle_slots::X) .coerce_to_number(activation)?; let y = rectangle - .get_public_property("y", activation)? + .get_slot(rectangle_slots::Y) .coerce_to_number(activation)?; let width = rectangle - .get_public_property("width", activation)? + .get_slot(rectangle_slots::WIDTH) .coerce_to_number(activation)?; let height = rectangle - .get_public_property("height", activation)? + .get_slot(rectangle_slots::HEIGHT) .coerce_to_number(activation)?; Some(Rectangle { x_min: Twips::from_pixels(x), diff --git a/core/src/avm2/globals/flash/events/GestureEvent.as b/core/src/avm2/globals/flash/events/GestureEvent.as index 3a0cb0c4af73..c1b877f6e93a 100644 --- a/core/src/avm2/globals/flash/events/GestureEvent.as +++ b/core/src/avm2/globals/flash/events/GestureEvent.as @@ -13,9 +13,11 @@ package flash.events public var phase: String; // The horizontal coordinate at which the event occurred relative to the containing sprite. + [Ruffle(InternalSlot)] public var localX: Number; // The vertical coordinate at which the event occurred relative to the containing sprite. + [Ruffle(InternalSlot)] public var localY: Number; // On Windows or Linux, indicates whether the Ctrl key is active (true) or inactive (false). diff --git a/core/src/avm2/globals/flash/events/MouseEvent.as b/core/src/avm2/globals/flash/events/MouseEvent.as index de141dbd3f0e..934d4a1e8d79 100644 --- a/core/src/avm2/globals/flash/events/MouseEvent.as +++ b/core/src/avm2/globals/flash/events/MouseEvent.as @@ -32,8 +32,13 @@ package flash.events public static const CONTEXT_MENU:String = "contextMenu"; public var relatedObject: InteractiveObject; + + [Ruffle(InternalSlot)] public var localX: Number; + + [Ruffle(InternalSlot)] public var localY: Number; + public var ctrlKey: Boolean; public var altKey: Boolean; public var shiftKey: Boolean; diff --git a/core/src/avm2/globals/flash/events/PressAndTapGestureEvent.as b/core/src/avm2/globals/flash/events/PressAndTapGestureEvent.as index 18cb54db6d7c..34d291fd8e6f 100644 --- a/core/src/avm2/globals/flash/events/PressAndTapGestureEvent.as +++ b/core/src/avm2/globals/flash/events/PressAndTapGestureEvent.as @@ -5,9 +5,11 @@ package flash.events { public static const GESTURE_PRESS_AND_TAP : String = "gesturePressAndTap"; + [Ruffle(InternalSlot)] private var _tapLocalX: Number; - private var _tapLocalY: Number; + [Ruffle(InternalSlot)] + private var _tapLocalY: Number; public function PressAndTapGestureEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false, phase:String = null, localX:Number = 0, localY:Number = 0, tapLocalX:Number = 0, tapLocalY:Number = 0, diff --git a/core/src/avm2/globals/flash/events/gesture_event.rs b/core/src/avm2/globals/flash/events/gesture_event.rs index 825542cf553a..f95daae15056 100644 --- a/core/src/avm2/globals/flash/events/gesture_event.rs +++ b/core/src/avm2/globals/flash/events/gesture_event.rs @@ -1,5 +1,6 @@ use crate::avm2::activation::Activation; use crate::avm2::globals::flash::events::mouse_event; +use crate::avm2::globals::slots::flash_events_gesture_event as slots; use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; @@ -9,7 +10,7 @@ pub fn get_stage_x<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - mouse_event::local_to_stage_x(activation, this, "localX", "localY") + mouse_event::local_to_stage_x(activation, this, slots::LOCAL_X, slots::LOCAL_Y) } pub fn get_stage_y<'gc>( @@ -17,5 +18,5 @@ pub fn get_stage_y<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - mouse_event::local_to_stage_y(activation, this, "localX", "localY") + mouse_event::local_to_stage_y(activation, this, slots::LOCAL_X, slots::LOCAL_Y) } diff --git a/core/src/avm2/globals/flash/events/mouse_event.rs b/core/src/avm2/globals/flash/events/mouse_event.rs index ae5680372cae..3c0b5cc7c0b0 100644 --- a/core/src/avm2/globals/flash/events/mouse_event.rs +++ b/core/src/avm2/globals/flash/events/mouse_event.rs @@ -1,4 +1,5 @@ use crate::avm2::activation::Activation; +use crate::avm2::globals::slots::flash_events_mouse_event as slots; use crate::avm2::object::{Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; @@ -11,7 +12,7 @@ pub fn get_stage_x<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - local_to_stage_x(activation, this, "localX", "localY") + local_to_stage_x(activation, this, slots::LOCAL_X, slots::LOCAL_Y) } /// Implements `stageY`'s getter. @@ -20,7 +21,7 @@ pub fn get_stage_y<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - local_to_stage_y(activation, this, "localX", "localY") + local_to_stage_y(activation, this, slots::LOCAL_X, slots::LOCAL_Y) } pub fn update_after_event<'gc>( @@ -35,16 +36,12 @@ pub fn update_after_event<'gc>( pub(super) fn local_to_stage_x<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, - label_x: &'static str, - label_y: &'static str, + slot_x: u32, + slot_y: u32, ) -> Result, Error<'gc>> { if let Some(evt) = this.as_event() { - let local_x = this - .get_public_property(label_x, activation)? - .coerce_to_number(activation)?; - let local_y = this - .get_public_property(label_y, activation)? - .coerce_to_number(activation)?; + let local_x = this.get_slot(slot_x).coerce_to_number(activation)?; + let local_y = this.get_slot(slot_y).coerce_to_number(activation)?; if local_x.is_nan() || local_y.is_nan() { return Ok(Value::Number(local_x)); @@ -65,16 +62,12 @@ pub(super) fn local_to_stage_x<'gc>( pub(super) fn local_to_stage_y<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, - label_x: &'static str, - label_y: &'static str, + slot_x: u32, + slot_y: u32, ) -> Result, Error<'gc>> { if let Some(evt) = this.as_event() { - let local_x = this - .get_public_property(label_x, activation)? - .coerce_to_number(activation)?; - let local_y = this - .get_public_property(label_y, activation)? - .coerce_to_number(activation)?; + let local_x = this.get_slot(slot_x).coerce_to_number(activation)?; + let local_y = this.get_slot(slot_y).coerce_to_number(activation)?; if local_x.is_nan() || local_y.is_nan() { return Ok(Value::Number(local_y)); diff --git a/core/src/avm2/globals/flash/events/press_and_tap_gesture_event.rs b/core/src/avm2/globals/flash/events/press_and_tap_gesture_event.rs index 2adcb34f84ef..745af49caaae 100644 --- a/core/src/avm2/globals/flash/events/press_and_tap_gesture_event.rs +++ b/core/src/avm2/globals/flash/events/press_and_tap_gesture_event.rs @@ -1,5 +1,6 @@ use crate::avm2::activation::Activation; use crate::avm2::globals::flash::events::mouse_event; +use crate::avm2::globals::slots::flash_events_press_and_tap_gesture_event as slots; use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; @@ -9,7 +10,7 @@ pub fn get_tap_stage_x<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - mouse_event::local_to_stage_x(activation, this, "localTapX", "localTapY") + mouse_event::local_to_stage_x(activation, this, slots::_TAP_LOCAL_X, slots::_TAP_LOCAL_Y) } pub fn get_tap_stage_y<'gc>( @@ -17,5 +18,5 @@ pub fn get_tap_stage_y<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - mouse_event::local_to_stage_y(activation, this, "localTapX", "localTapY") + mouse_event::local_to_stage_y(activation, this, slots::_TAP_LOCAL_X, slots::_TAP_LOCAL_Y) } diff --git a/core/src/avm2/globals/flash/geom/ColorTransform.as b/core/src/avm2/globals/flash/geom/ColorTransform.as index 32f563cfe989..98d0883f794b 100644 --- a/core/src/avm2/globals/flash/geom/ColorTransform.as +++ b/core/src/avm2/globals/flash/geom/ColorTransform.as @@ -1,12 +1,27 @@ package flash.geom { public class ColorTransform { + [Ruffle(InternalSlot)] public var redMultiplier: Number; + + [Ruffle(InternalSlot)] public var greenMultiplier: Number; + + [Ruffle(InternalSlot)] public var blueMultiplier: Number; + + [Ruffle(InternalSlot)] public var alphaMultiplier: Number; + + [Ruffle(InternalSlot)] public var redOffset: Number; + + [Ruffle(InternalSlot)] public var greenOffset: Number; + + [Ruffle(InternalSlot)] public var blueOffset: Number; + + [Ruffle(InternalSlot)] public var alphaOffset: Number; public function ColorTransform(redMultiplier: Number = 1, diff --git a/core/src/avm2/globals/flash/geom/Matrix.as b/core/src/avm2/globals/flash/geom/Matrix.as index 46eda5ada246..bb6688cba8d6 100644 --- a/core/src/avm2/globals/flash/geom/Matrix.as +++ b/core/src/avm2/globals/flash/geom/Matrix.as @@ -1,198 +1,209 @@ package flash.geom { - public class Matrix { - public var a:Number; - public var b:Number; - public var c:Number; - public var d:Number; - public var tx:Number; - public var ty:Number; - - public function Matrix(a:Number = 1, b:Number = 0, c:Number = 0, d:Number = 1, tx:Number = 0, ty:Number = 0) { - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - } - - public function clone():Matrix { - return new Matrix(this.a,this.b,this.c,this.d,this.tx,this.ty); - } - - public function concat(m:Matrix):void { - var a = m.a * this.a + m.c * this.b; - var b = m.b * this.a + m.d * this.b; - var c = m.a * this.c + m.c * this.d; - var d = m.b * this.c + m.d * this.d; - var tx = m.a * this.tx + m.c * this.ty + m.tx; - var ty = m.b * this.tx + m.d * this.ty + m.ty; - - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - } - - [API("674")] - public function copyColumnFrom(column:uint, vector3D:Vector3D):void { - // FP BUG: For some reason these methods are identical - this.copyRowFrom(column, vector3D); - } - - [API("674")] - public function copyColumnTo(column:uint, vector3D:Vector3D):void { - if(column == 0) { - vector3D.x = this.a; - vector3D.y = this.b; - vector3D.z = 0; - } - else if (column == 1) { - vector3D.x = this.c; - vector3D.y = this.d; - vector3D.z = 0; - } - else if (column == 2) { - vector3D.x = this.tx; - vector3D.y = this.ty; - vector3D.z = 1; - } // otherwise vector is unchanged - } - - [API("674")] - public function copyFrom(sourceMatrix: Matrix): void { - this.a = sourceMatrix.a; - this.b = sourceMatrix.b; - this.c = sourceMatrix.c; - this.d = sourceMatrix.d; - this.tx = sourceMatrix.tx; - this.ty = sourceMatrix.ty; - } - - [API("674")] - public function copyRowFrom(row: uint, vector3D: Vector3D): void { - if (row == 0) { - this.a = vector3D.x; - this.c = vector3D.y; - this.tx = vector3D.z; - } else if(row == 1) { - this.b = vector3D.x; - this.d = vector3D.y; - this.ty = vector3D.z; - } // otherwise matrix is unchanged - } - - [API("674")] - public function copyRowTo(row:uint, vector3D:Vector3D):void { - if(row == 0) { - vector3D.x = this.a; - vector3D.y = this.c; - vector3D.z = this.tx; - } - else if (row == 1) { - vector3D.x = this.b; - vector3D.y = this.d; - vector3D.z = this.ty; - } - else if (row == 2) { - vector3D.x = 0; - vector3D.y = 0; - vector3D.z = 1; - } // otherwise vector is unchanged - } - - public function createBox(scaleX:Number, scaleY:Number, rotation:Number = 0, tx:Number = 0, ty:Number = 0):void { - this.identity(); - this.rotate(rotation); - this.scale(scaleX, scaleY); - this.translate(tx, ty); - } - - public function createGradientBox(width: Number, height: Number, rotation: Number = 0, tx: Number = 0, ty: Number = 0): void { - this.createBox(width / 1638.4, height / 1638.4, rotation, tx + width / 2, ty + height / 2); - } - - public function deltaTransformPoint(point:Point):Point { - return new Point(this.a * point.x + this.c * point.y, this.b * point.x + this.d * point.y); - } - - public function identity():void { - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - } - - public function invert():void { - var det = this.a * this.d - this.c * this.b; - var tx = (this.d * this.tx - this.c * this.ty) / -det; - var ty = (this.b * this.tx - this.a * this.ty) / det; - var a = this.d / det; - var b = this.b / -det; - var c = this.c / -det; - var d = this.a / det; - - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - } - - public function rotate(angle:Number):void { - var sin = Math.sin(angle); - var cos = Math.cos(angle); - - var a = cos * this.a + (-sin) * this.b; - var b = sin * this.a + cos * this.b; - var c = cos * this.c + (-sin) * this.d; - var d = sin * this.c + cos * this.d; - var tx = cos * this.tx + (-sin) * this.ty; - var ty = sin * this.tx + cos * this.ty; - - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - } - - public function scale(sx:Number, sy:Number):void { - this.a *= sx; - this.b *= sy; - this.c *= sx; - this.d *= sy; - this.tx *= sx; - this.ty *= sy; - } - - [API("674")] - public function setTo(aa:Number, ba:Number, ca:Number, da:Number, txa:Number, tya:Number):void { - this.a = aa; - this.b = ba; - this.c = ca; - this.d = da; - this.tx = txa; - this.ty = tya; - } - - public function toString():String { - return "(a=" + this.a + ", b=" + this.b + ", c=" + this.c + ", d=" + this.d + ", tx=" + this.tx + ", ty=" + this.ty + ")"; - } - - public function transformPoint(point:Point):Point { - return new Point(this.a * point.x + this.c * point.y + this.tx, this.b * point.x + this.d * point.y + this.ty); - } - - public function translate(dx:Number, dy:Number):void { - this.tx += dx; - this.ty += dy; - } - } + public class Matrix { + [Ruffle(InternalSlot)] + public var a:Number; + + [Ruffle(InternalSlot)] + public var b:Number; + + [Ruffle(InternalSlot)] + public var c:Number; + + [Ruffle(InternalSlot)] + public var d:Number; + + [Ruffle(InternalSlot)] + public var tx:Number; + + [Ruffle(InternalSlot)] + public var ty:Number; + + public function Matrix(a:Number = 1, b:Number = 0, c:Number = 0, d:Number = 1, tx:Number = 0, ty:Number = 0) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + } + + public function clone():Matrix { + return new Matrix(this.a,this.b,this.c,this.d,this.tx,this.ty); + } + + public function concat(m:Matrix):void { + var a = m.a * this.a + m.c * this.b; + var b = m.b * this.a + m.d * this.b; + var c = m.a * this.c + m.c * this.d; + var d = m.b * this.c + m.d * this.d; + var tx = m.a * this.tx + m.c * this.ty + m.tx; + var ty = m.b * this.tx + m.d * this.ty + m.ty; + + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + } + + [API("674")] + public function copyColumnFrom(column:uint, vector3D:Vector3D):void { + // FP BUG: For some reason these methods are identical + this.copyRowFrom(column, vector3D); + } + + [API("674")] + public function copyColumnTo(column:uint, vector3D:Vector3D):void { + if(column == 0) { + vector3D.x = this.a; + vector3D.y = this.b; + vector3D.z = 0; + } + else if (column == 1) { + vector3D.x = this.c; + vector3D.y = this.d; + vector3D.z = 0; + } + else if (column == 2) { + vector3D.x = this.tx; + vector3D.y = this.ty; + vector3D.z = 1; + } // otherwise vector is unchanged + } + + [API("674")] + public function copyFrom(sourceMatrix: Matrix): void { + this.a = sourceMatrix.a; + this.b = sourceMatrix.b; + this.c = sourceMatrix.c; + this.d = sourceMatrix.d; + this.tx = sourceMatrix.tx; + this.ty = sourceMatrix.ty; + } + + [API("674")] + public function copyRowFrom(row: uint, vector3D: Vector3D): void { + if (row == 0) { + this.a = vector3D.x; + this.c = vector3D.y; + this.tx = vector3D.z; + } else if(row == 1) { + this.b = vector3D.x; + this.d = vector3D.y; + this.ty = vector3D.z; + } // otherwise matrix is unchanged + } + + [API("674")] + public function copyRowTo(row:uint, vector3D:Vector3D):void { + if(row == 0) { + vector3D.x = this.a; + vector3D.y = this.c; + vector3D.z = this.tx; + } + else if (row == 1) { + vector3D.x = this.b; + vector3D.y = this.d; + vector3D.z = this.ty; + } + else if (row == 2) { + vector3D.x = 0; + vector3D.y = 0; + vector3D.z = 1; + } // otherwise vector is unchanged + } + + public function createBox(scaleX:Number, scaleY:Number, rotation:Number = 0, tx:Number = 0, ty:Number = 0):void { + this.identity(); + this.rotate(rotation); + this.scale(scaleX, scaleY); + this.translate(tx, ty); + } + + public function createGradientBox(width: Number, height: Number, rotation: Number = 0, tx: Number = 0, ty: Number = 0): void { + this.createBox(width / 1638.4, height / 1638.4, rotation, tx + width / 2, ty + height / 2); + } + + public function deltaTransformPoint(point:Point):Point { + return new Point(this.a * point.x + this.c * point.y, this.b * point.x + this.d * point.y); + } + + public function identity():void { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + } + + public function invert():void { + var det = this.a * this.d - this.c * this.b; + var tx = (this.d * this.tx - this.c * this.ty) / -det; + var ty = (this.b * this.tx - this.a * this.ty) / det; + var a = this.d / det; + var b = this.b / -det; + var c = this.c / -det; + var d = this.a / det; + + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + } + + public function rotate(angle:Number):void { + var sin = Math.sin(angle); + var cos = Math.cos(angle); + + var a = cos * this.a + (-sin) * this.b; + var b = sin * this.a + cos * this.b; + var c = cos * this.c + (-sin) * this.d; + var d = sin * this.c + cos * this.d; + var tx = cos * this.tx + (-sin) * this.ty; + var ty = sin * this.tx + cos * this.ty; + + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + } + + public function scale(sx:Number, sy:Number):void { + this.a *= sx; + this.b *= sy; + this.c *= sx; + this.d *= sy; + this.tx *= sx; + this.ty *= sy; + } + + [API("674")] + public function setTo(aa:Number, ba:Number, ca:Number, da:Number, txa:Number, tya:Number):void { + this.a = aa; + this.b = ba; + this.c = ca; + this.d = da; + this.tx = txa; + this.ty = tya; + } + + public function toString():String { + return "(a=" + this.a + ", b=" + this.b + ", c=" + this.c + ", d=" + this.d + ", tx=" + this.tx + ", ty=" + this.ty + ")"; + } + + public function transformPoint(point:Point):Point { + return new Point(this.a * point.x + this.c * point.y + this.tx, this.b * point.x + this.d * point.y + this.ty); + } + + public function translate(dx:Number, dy:Number):void { + this.tx += dx; + this.ty += dy; + } + } } diff --git a/core/src/avm2/globals/flash/geom/Matrix3D.as b/core/src/avm2/globals/flash/geom/Matrix3D.as index 390bebc88f1e..b1151179cffb 100644 --- a/core/src/avm2/globals/flash/geom/Matrix3D.as +++ b/core/src/avm2/globals/flash/geom/Matrix3D.as @@ -4,9 +4,9 @@ package flash.geom { import __ruffle__.stub_method; public class Matrix3D { - // The 4x4 matrix data, stored in column-major order // This is never null. + [Ruffle(InternalSlot)] private var _rawData:Vector.; public function get rawData():Vector. { @@ -30,11 +30,11 @@ package flash.geom { public function identity():void { // Note that every 4 elements is a *column*, not a row this._rawData = new [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ]; + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]; } public function appendTranslation(x:Number, y:Number, z:Number):void { diff --git a/core/src/avm2/globals/flash/geom/Point.as b/core/src/avm2/globals/flash/geom/Point.as index a2265e8eae93..c8826aee14b0 100644 --- a/core/src/avm2/globals/flash/geom/Point.as +++ b/core/src/avm2/globals/flash/geom/Point.as @@ -1,73 +1,76 @@ package flash.geom { - public class Point { - public var x:Number; - public var y:Number; + public class Point { + [Ruffle(InternalSlot)] + public var x:Number; - public function Point(x:Number = 0, y:Number = 0) { - this.x = x; - this.y = y; - } + [Ruffle(InternalSlot)] + public var y:Number; - public function get length():Number { - return Math.sqrt(this.x * this.x + this.y * this.y); - } + public function Point(x:Number = 0, y:Number = 0) { + this.x = x; + this.y = y; + } - public function toString():String { - return "(x=" + this.x + ", y=" + this.y + ")"; - } + public function get length():Number { + return Math.sqrt(this.x * this.x + this.y * this.y); + } - public function add(v:Point):Point { - return new Point(this.x + v.x, this.y + v.y); - } + public function toString():String { + return "(x=" + this.x + ", y=" + this.y + ")"; + } - public function subtract(v:Point):Point { - return new Point(this.x - v.x, this.y - v.y); - } + public function add(v:Point):Point { + return new Point(this.x + v.x, this.y + v.y); + } - public function clone():Point { - return new Point(this.x, this.y); - } + public function subtract(v:Point):Point { + return new Point(this.x - v.x, this.y - v.y); + } - [API("674")] - public function copyFrom(sourcePoint:Point):void { - this.x = sourcePoint.x; - this.y = sourcePoint.y; - } + public function clone():Point { + return new Point(this.x, this.y); + } - public function equals(toCompare:Point):Boolean { - return this.x == toCompare.x && this.y == toCompare.y; - } + [API("674")] + public function copyFrom(sourcePoint:Point):void { + this.x = sourcePoint.x; + this.y = sourcePoint.y; + } - public function normalize(thickness:Number):void { - var len:Number = this.length; - if (len > 0) { - var inv_d:Number = thickness / len; - this.x *= inv_d; - this.y *= inv_d; - } - } + public function equals(toCompare:Point):Boolean { + return this.x == toCompare.x && this.y == toCompare.y; + } - public function offset(dx:Number, dy:Number):void { - this.x += dx; - this.y += dy; - } + public function normalize(thickness:Number):void { + var len:Number = this.length; + if (len > 0) { + var inv_d:Number = thickness / len; + this.x *= inv_d; + this.y *= inv_d; + } + } - [API("674")] - public function setTo(xa:Number, ya:Number):void { - this.x = xa; - this.y = ya; - } + public function offset(dx:Number, dy:Number):void { + this.x += dx; + this.y += dy; + } - public static function distance(pt1:Point, pt2:Point):Number { - return pt2.subtract(pt1).length; - } + [API("674")] + public function setTo(xa:Number, ya:Number):void { + this.x = xa; + this.y = ya; + } - public static function interpolate(pt1:Point, pt2:Point, f:Number):Point { - return new Point(pt2.x - (pt2.x - pt1.x) * f, pt2.y - (pt2.y - pt1.y) * f); - } + public static function distance(pt1:Point, pt2:Point):Number { + return pt2.subtract(pt1).length; + } - public static function polar(len:Number, angle:Number):Point { - return new Point(len* Math.cos(angle), len * Math.sin(angle)); - } - } + public static function interpolate(pt1:Point, pt2:Point, f:Number):Point { + return new Point(pt2.x - (pt2.x - pt1.x) * f, pt2.y - (pt2.y - pt1.y) * f); + } + + public static function polar(len:Number, angle:Number):Point { + return new Point(len* Math.cos(angle), len * Math.sin(angle)); + } + } } diff --git a/core/src/avm2/globals/flash/geom/Rectangle.as b/core/src/avm2/globals/flash/geom/Rectangle.as index 51d443c35184..8fbd5ff4a07d 100644 --- a/core/src/avm2/globals/flash/geom/Rectangle.as +++ b/core/src/avm2/globals/flash/geom/Rectangle.as @@ -1,8 +1,15 @@ package flash.geom { public class Rectangle { + [Ruffle(InternalSlot)] public var x: Number; + + [Ruffle(InternalSlot)] public var y: Number; + + [Ruffle(InternalSlot)] public var width: Number; + + [Ruffle(InternalSlot)] public var height: Number; public function Rectangle(x: Number = 0, y: Number = 0, width: Number = 0, height: Number = 0) { diff --git a/core/src/avm2/globals/flash/geom/Transform.as b/core/src/avm2/globals/flash/geom/Transform.as index a77b346a9f51..b305d61254fb 100644 --- a/core/src/avm2/globals/flash/geom/Transform.as +++ b/core/src/avm2/globals/flash/geom/Transform.as @@ -1,40 +1,44 @@ package flash.geom { - import flash.display.DisplayObject; - import flash.geom.Matrix3D; - import flash.geom.PerspectiveProjection; - import __ruffle__.stub_getter; - import __ruffle__.stub_method; - - public class Transform { - [Ruffle(InternalSlot)] - private var displayObject:DisplayObject; - - function Transform(object:DisplayObject) { - this.displayObject = object; - } - - public native function get colorTransform():ColorTransform; - public native function set colorTransform(value:ColorTransform):void; - public native function get matrix():Matrix; - public native function set matrix(value:Matrix):void; - - public function get concatenatedColorTransform():ColorTransform { - stub_getter("flash.geom.Transform", "concatenatedColorTransform"); - return new ColorTransform(); - } - - public native function get concatenatedMatrix():Matrix; - public native function get pixelBounds():Rectangle; - - public native function get matrix3D():Matrix3D; - public native function set matrix3D(m:Matrix3D):void; - - public native function get perspectiveProjection():PerspectiveProjection; - public native function set perspectiveProjection(val: PerspectiveProjection):void; - - public function getRelativeMatrix3D(relativeTo:DisplayObject):Matrix3D { - stub_method("flash.geom.Transform", "getRelativeMatrix3D"); - return new Matrix3D(); - } - } + import flash.display.DisplayObject; + import flash.geom.Matrix3D; + import flash.geom.PerspectiveProjection; + import __ruffle__.stub_getter; + import __ruffle__.stub_method; + import __ruffle__.stub_setter; + + public class Transform { + [Ruffle(InternalSlot)] + private var displayObject:DisplayObject; + + private var _matrix3D:Matrix3D = null; + private var _perspectiveProjection:PerspectiveProjection = null; + + function Transform(object:DisplayObject) { + this.displayObject = object; + } + + public native function get colorTransform():ColorTransform; + public native function set colorTransform(value:ColorTransform):void; + public native function get matrix():Matrix; + public native function set matrix(value:Matrix):void; + + public function get concatenatedColorTransform():ColorTransform { + stub_getter("flash.geom.Transform", "concatenatedColorTransform"); + return new ColorTransform(); + } + + public native function get concatenatedMatrix():Matrix; + public native function get pixelBounds():Rectangle; + + public native function get matrix3D():Matrix3D; + public native function set matrix3D(m:Matrix3D):void; + + public native function get perspectiveProjection():PerspectiveProjection; + public native function set perspectiveProjection(val: PerspectiveProjection):void; + + public function getRelativeMatrix3D(relativeTo:DisplayObject):Matrix3D { + stub_method("flash.geom.Transform", "getRelativeMatrix3D"); + return new Matrix3D(); + } + } } diff --git a/core/src/avm2/globals/flash/geom/transform.rs b/core/src/avm2/globals/flash/geom/transform.rs index d0454024d37c..99139599aac7 100644 --- a/core/src/avm2/globals/flash/geom/transform.rs +++ b/core/src/avm2/globals/flash/geom/transform.rs @@ -1,4 +1,6 @@ -use crate::avm2::globals::slots::flash_geom_transform as slots; +use crate::avm2::globals::slots::flash_geom_color_transform as ct_slots; +use crate::avm2::globals::slots::flash_geom_matrix as matrix_slots; +use crate::avm2::globals::slots::flash_geom_transform as transform_slots; use crate::avm2::parameters::ParametersExt; use crate::avm2::{Activation, Error, Object, TObject, Value}; use crate::display_object::TDisplayObject; @@ -7,16 +9,12 @@ use crate::{avm2_stub_getter, avm2_stub_setter}; use ruffle_render::quality::StageQuality; use swf::{ColorTransform, Fixed8, Rectangle}; -fn get_display_object<'gc>( - this: Object<'gc>, - _activation: &mut Activation<'_, 'gc>, -) -> Result, Error<'gc>> { - Ok(this - .get_slot(slots::DISPLAY_OBJECT) +fn get_display_object(this: Object<'_>) -> DisplayObject<'_> { + this.get_slot(transform_slots::DISPLAY_OBJECT) .as_object() .unwrap() .as_display_object() - .unwrap()) + .unwrap() } pub fn get_color_transform<'gc>( @@ -24,9 +22,8 @@ pub fn get_color_transform<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let display_object = get_display_object(this, activation)?; - let display_object = display_object.base(); - color_transform_to_object(display_object.color_transform(), activation) + let color_transform = color_transform_from_transform_object(this); + color_transform_to_object(&color_transform, activation) } pub fn set_color_transform<'gc>( @@ -38,7 +35,7 @@ pub fn set_color_transform<'gc>( args.get_object(activation, 0, "colorTransform")?, activation, )?; - let dobj = get_display_object(this, activation)?; + let dobj = get_display_object(this); dobj.set_color_transform(activation.context.gc_context, ct); if let Some(parent) = dobj.parent() { parent.invalidate_cached_bitmap(activation.context.gc_context); @@ -51,7 +48,7 @@ pub fn get_matrix<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let matrix = *get_display_object(this, activation)?.base().matrix(); + let matrix = matrix_from_transform_object(this); matrix_to_object(matrix, activation) } @@ -64,7 +61,7 @@ pub fn set_matrix<'gc>( // null when trying to get the matrix- but the DO's actual transform matrix will // remain its previous non-null value. let matrix = object_to_matrix(args.get_object(activation, 0, "value")?, activation)?; - let dobj = get_display_object(this, activation)?; + let dobj = get_display_object(this); dobj.set_matrix(activation.context.gc_context, matrix); if let Some(parent) = dobj.parent() { // Self-transform changes are automatically handled, @@ -79,7 +76,7 @@ pub fn get_concatenated_matrix<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let dobj = get_display_object(this, activation)?; + let dobj = get_display_object(this); let mut node = Some(dobj); while let Some(obj) = node { if obj.as_stage().is_some() { @@ -90,8 +87,7 @@ pub fn get_concatenated_matrix<'gc>( // We're a child of the Stage, and not the stage itself if node.is_some() && dobj.as_stage().is_none() { - let matrix = - get_display_object(this, activation)?.local_to_global_matrix_without_own_scroll_rect(); + let matrix = get_display_object(this).local_to_global_matrix_without_own_scroll_rect(); matrix_to_object(matrix, activation) } else { // If this object is the Stage itself, or an object @@ -113,6 +109,16 @@ pub fn get_concatenated_matrix<'gc>( } } +pub fn matrix_from_transform_object(transform_object: Object<'_>) -> Matrix { + *get_display_object(transform_object).base().matrix() +} + +pub fn color_transform_from_transform_object(transform_object: Object<'_>) -> ColorTransform { + *get_display_object(transform_object) + .base() + .color_transform() +} + // FIXME - handle clamping. We're throwing away precision here in converting to an integer: // is that what we should be doing? pub fn object_to_color_transform<'gc>( @@ -120,29 +126,30 @@ pub fn object_to_color_transform<'gc>( activation: &mut Activation<'_, 'gc>, ) -> Result> { let red_multiplier = object - .get_public_property("redMultiplier", activation)? + .get_slot(ct_slots::RED_MULTIPLIER) .coerce_to_number(activation)?; let green_multiplier = object - .get_public_property("greenMultiplier", activation)? + .get_slot(ct_slots::GREEN_MULTIPLIER) .coerce_to_number(activation)?; let blue_multiplier = object - .get_public_property("blueMultiplier", activation)? + .get_slot(ct_slots::BLUE_MULTIPLIER) .coerce_to_number(activation)?; let alpha_multiplier = object - .get_public_property("alphaMultiplier", activation)? + .get_slot(ct_slots::ALPHA_MULTIPLIER) .coerce_to_number(activation)?; let red_offset = object - .get_public_property("redOffset", activation)? + .get_slot(ct_slots::RED_OFFSET) .coerce_to_number(activation)?; let green_offset = object - .get_public_property("greenOffset", activation)? + .get_slot(ct_slots::GREEN_OFFSET) .coerce_to_number(activation)?; let blue_offset = object - .get_public_property("blueOffset", activation)? + .get_slot(ct_slots::BLUE_OFFSET) .coerce_to_number(activation)?; let alpha_offset = object - .get_public_property("alphaOffset", activation)? + .get_slot(ct_slots::ALPHA_OFFSET) .coerce_to_number(activation)?; + Ok(ColorTransform { r_multiply: Fixed8::from_f64(red_multiplier), g_multiply: Fixed8::from_f64(green_multiplier), @@ -199,25 +206,25 @@ pub fn object_to_matrix<'gc>( activation: &mut Activation<'_, 'gc>, ) -> Result> { let a = object - .get_public_property("a", activation)? + .get_slot(matrix_slots::A) .coerce_to_number(activation)? as f32; let b = object - .get_public_property("b", activation)? + .get_slot(matrix_slots::B) .coerce_to_number(activation)? as f32; let c = object - .get_public_property("c", activation)? + .get_slot(matrix_slots::C) .coerce_to_number(activation)? as f32; let d = object - .get_public_property("d", activation)? + .get_slot(matrix_slots::D) .coerce_to_number(activation)? as f32; let tx = Twips::from_pixels( object - .get_public_property("tx", activation)? + .get_slot(matrix_slots::TX) .coerce_to_number(activation)?, ); let ty = Twips::from_pixels( object - .get_public_property("ty", activation)? + .get_slot(matrix_slots::TY) .coerce_to_number(activation)?, ); @@ -229,7 +236,7 @@ pub fn get_pixel_bounds<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let display_object = get_display_object(this, activation)?; + let display_object = get_display_object(this); rectangle_to_object(display_object.world_bounds(), activation) } @@ -256,7 +263,7 @@ pub fn get_matrix_3d<'gc>( ) -> Result, Error<'gc>> { avm2_stub_getter!(activation, "flash.geom.Transform", "matrix3D"); - let display_object = get_display_object(this, activation)?; + let display_object = get_display_object(this); if display_object.base().has_matrix3d_stub() { let object = activation .avm2() @@ -280,7 +287,7 @@ pub fn set_matrix_3d<'gc>( .get(0) .map(|arg| arg.as_object().is_some()) .unwrap_or_default(); - let display_object = get_display_object(this, activation)?; + let display_object = get_display_object(this); display_object .base_mut(activation.gc()) .set_has_matrix3d_stub(set); @@ -294,7 +301,7 @@ pub fn get_perspective_projection<'gc>( ) -> Result, Error<'gc>> { avm2_stub_getter!(activation, "flash.geom.Transform", "perspectiveProjection"); - let display_object = get_display_object(this, activation)?; + let display_object = get_display_object(this); let has_perspective_projection = if display_object.is_root() { true } else { @@ -324,7 +331,7 @@ pub fn set_perspective_projection<'gc>( .get(0) .map(|arg| arg.as_object().is_some()) .unwrap_or_default(); - let display_object = get_display_object(this, activation)?; + let display_object = get_display_object(this); display_object .base_mut(activation.gc()) .set_has_perspective_projection_stub(set); diff --git a/core/src/avm2/globals/flash/media/ID3Info.as b/core/src/avm2/globals/flash/media/ID3Info.as index 26e1bdf312c9..ca6c8a4c4f20 100644 --- a/core/src/avm2/globals/flash/media/ID3Info.as +++ b/core/src/avm2/globals/flash/media/ID3Info.as @@ -1,12 +1,24 @@ package flash.media { - public final dynamic class ID3Info { + [Ruffle(InternalSlot)] public var album:String; + + [Ruffle(InternalSlot)] public var artist:String; + + [Ruffle(InternalSlot)] public var comment:String; + + [Ruffle(InternalSlot)] public var genre:String; + + [Ruffle(InternalSlot)] public var songName:String; + + [Ruffle(InternalSlot)] public var track:String; + + [Ruffle(InternalSlot)] public var year:String; } } diff --git a/core/src/avm2/globals/flash/media/SoundTransform.as b/core/src/avm2/globals/flash/media/SoundTransform.as index 3dd33f73cde6..5d0cdd3c588d 100644 --- a/core/src/avm2/globals/flash/media/SoundTransform.as +++ b/core/src/avm2/globals/flash/media/SoundTransform.as @@ -1,17 +1,27 @@ package flash.media { - public final class SoundTransform { - public var leftToLeft:Number; - public var leftToRight:Number; - public var rightToLeft:Number; - public var rightToRight:Number; - public var volume:Number; - + [Ruffle(InstanceAllocator)] + public final class SoundTransform { public function SoundTransform(vol:Number = 1, panning:Number = 0) { this.volume = vol; this.pan = panning; } + public native function get leftToLeft():Number; + public native function set leftToLeft(value:Number):void; + + public native function get leftToRight():Number; + public native function set leftToRight(value:Number):void; + + public native function get rightToLeft():Number; + public native function set rightToLeft(value:Number):void; + + public native function get rightToRight():Number; + public native function set rightToRight(value:Number):void; + + public native function get volume():Number; + public native function set volume(volume:Number):void; + public native function get pan():Number; public native function set pan(value:Number):void; } -} \ No newline at end of file +} diff --git a/core/src/avm2/globals/flash/media/sound.rs b/core/src/avm2/globals/flash/media/sound.rs index f6ce1c034888..4b443427636b 100644 --- a/core/src/avm2/globals/flash/media/sound.rs +++ b/core/src/avm2/globals/flash/media/sound.rs @@ -1,6 +1,7 @@ //! `flash.media.Sound` builtin/prototype use crate::avm2::activation::Activation; +use crate::avm2::globals::slots::flash_net_url_request as url_request_slots; use crate::avm2::object::{Object, QueuedPlay, SoundChannelObject, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; @@ -164,10 +165,7 @@ pub fn play<'gc>( }; let sound_transform = if let Some(sound_transform) = sound_transform { - Some(SoundTransform::from_avm2_object( - activation, - sound_transform, - )?) + Some(SoundTransform::from_avm2_object(sound_transform)) } else { None }; @@ -237,7 +235,7 @@ pub fn load<'gc>( }; let url = url_request - .get_public_property("url", activation)? + .get_slot(url_request_slots::_URL) .coerce_to_string(activation)?; // TODO: context parameter currently unused. diff --git a/core/src/avm2/globals/flash/media/sound_channel.rs b/core/src/avm2/globals/flash/media/sound_channel.rs index 8a2ce0eb1543..696ce12d973a 100644 --- a/core/src/avm2/globals/flash/media/sound_channel.rs +++ b/core/src/avm2/globals/flash/media/sound_channel.rs @@ -80,7 +80,7 @@ pub fn set_sound_transform<'gc>( ) -> Result, Error<'gc>> { if let Some(sound_channel) = this.as_sound_channel() { let as3_st = args.get_object(activation, 0, "soundChannel")?; - let dobj_st = SoundTransform::from_avm2_object(activation, as3_st)?; + let dobj_st = SoundTransform::from_avm2_object(as3_st); sound_channel.set_sound_transform(activation, dobj_st); } diff --git a/core/src/avm2/globals/flash/media/sound_mixer.rs b/core/src/avm2/globals/flash/media/sound_mixer.rs index cbdc77b58646..def2bafebf69 100644 --- a/core/src/avm2/globals/flash/media/sound_mixer.rs +++ b/core/src/avm2/globals/flash/media/sound_mixer.rs @@ -34,7 +34,7 @@ pub fn set_sound_transform<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { let as3_st = args.get_object(activation, 0, "sndTransform")?; - let dobj_st = SoundTransform::from_avm2_object(activation, as3_st)?; + let dobj_st = SoundTransform::from_avm2_object(as3_st); activation.context.set_global_sound_transform(dobj_st); diff --git a/core/src/avm2/globals/flash/media/sound_transform.rs b/core/src/avm2/globals/flash/media/sound_transform.rs index 5f5d29c6b0b5..064539118458 100644 --- a/core/src/avm2/globals/flash/media/sound_transform.rs +++ b/core/src/avm2/globals/flash/media/sound_transform.rs @@ -6,26 +6,139 @@ use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; use crate::avm2::Error; +pub use crate::avm2::object::sound_transform_allocator; + +pub fn get_left_to_left<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + + Ok(this.left_to_left().into()) +} + +pub fn set_left_to_left<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + + let value = args.get_f64(activation, 0)?; + this.set_left_to_left(value); + + Ok(Value::Undefined) +} + +pub fn get_left_to_right<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + + Ok(this.left_to_right().into()) +} + +pub fn set_left_to_right<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + + let value = args.get_f64(activation, 0)?; + this.set_left_to_right(value); + + Ok(Value::Undefined) +} + +pub fn get_right_to_left<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + + Ok(this.right_to_left().into()) +} + +pub fn set_right_to_left<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + + let value = args.get_f64(activation, 0)?; + this.set_right_to_left(value); + + Ok(Value::Undefined) +} + +pub fn get_right_to_right<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + + Ok(this.right_to_right().into()) +} + +pub fn set_right_to_right<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + + let value = args.get_f64(activation, 0)?; + this.set_right_to_right(value); + + Ok(Value::Undefined) +} + +pub fn get_volume<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + + Ok(this.volume().into()) +} + +pub fn set_volume<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + + let value = args.get_f64(activation, 0)?; + this.set_volume(value); + + Ok(Value::Undefined) +} + /// Implements `SoundTransform.pan`'s getter. pub fn get_pan<'gc>( - activation: &mut Activation<'_, 'gc>, + _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let left_to_right = this - .get_public_property("leftToRight", activation)? - .coerce_to_number(activation)?; - let right_to_left = this - .get_public_property("rightToLeft", activation)? - .coerce_to_number(activation)?; + let this = this.as_sound_transform().unwrap(); + + let left_to_right = this.left_to_right(); + let right_to_left = this.right_to_left(); if left_to_right != 0.0 || right_to_left != 0.0 { return Ok(0.0.into()); } - let left_to_left = this - .get_public_property("leftToLeft", activation)? - .coerce_to_number(activation)?; + let left_to_left = this.left_to_left(); Ok((1.0 - left_to_left.powf(2.0)).into()) } @@ -36,11 +149,14 @@ pub fn set_pan<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { + let this = this.as_sound_transform().unwrap(); + let pan = args.get_f64(activation, 0)?; - this.set_public_property("leftToLeft", (1.0 - pan).sqrt().into(), activation)?; - this.set_public_property("rightToRight", (1.0 + pan).sqrt().into(), activation)?; - this.set_public_property("leftToRight", (0.0).into(), activation)?; - this.set_public_property("rightToLeft", (0.0).into(), activation)?; + + this.set_left_to_left((1.0 - pan).sqrt()); + this.set_right_to_right((1.0 + pan).sqrt()); + this.set_left_to_right(0.0); + this.set_right_to_left(0.0); Ok(Value::Undefined) } diff --git a/core/src/avm2/globals/flash/net.rs b/core/src/avm2/globals/flash/net.rs index c95b48f6b6f7..07dd7422b396 100644 --- a/core/src/avm2/globals/flash/net.rs +++ b/core/src/avm2/globals/flash/net.rs @@ -1,6 +1,7 @@ //! `flash.net` namespace use crate::avm2::error::{make_error_1014, make_error_2007, Error1014Type}; +use crate::avm2::globals::slots::flash_net_url_request as url_request_slots; use crate::avm2::object::TObject; use crate::avm2::parameters::ParametersExt; use crate::avm2::{Activation, Error, Object, Value}; @@ -80,21 +81,23 @@ pub fn navigate_to_url<'gc>( let target = args.get_string(activation, 1)?; - match request.get_public_property("url", activation)? { + match request.get_slot(url_request_slots::_URL) { Value::Null => Err(make_error_2007(activation, "url")), url => { let url = url.coerce_to_string(activation)?.to_string(); let method = request - .get_public_property("method", activation)? + .get_slot(url_request_slots::_METHOD) .coerce_to_string(activation)?; let method = NavigationMethod::from_method_str(&method).unwrap(); - let data: Value<'gc> = request.get_public_property("data", activation)?; + let data = request.get_slot(url_request_slots::_DATA); let (url, vars) = parse_data(activation, &url, &data)?; + activation.context.navigator.navigate_to_url( &url, &target.to_utf8_lossy(), Some((method, vars)), ); + Ok(Value::Undefined) } } diff --git a/core/src/avm2/globals/flash/net/FileFilter.as b/core/src/avm2/globals/flash/net/FileFilter.as index 004958cdcc9c..6a6d0972af3e 100644 --- a/core/src/avm2/globals/flash/net/FileFilter.as +++ b/core/src/avm2/globals/flash/net/FileFilter.as @@ -1,7 +1,12 @@ package flash.net { public final class FileFilter { + [Ruffle(InternalSlot)] private var _description:String; + + [Ruffle(InternalSlot)] private var _extension:String; + + [Ruffle(InternalSlot)] private var _macType:String; public function FileFilter(description:String, extension:String, macType:String = null) { @@ -35,4 +40,4 @@ package flash.net { } } -} \ No newline at end of file +} diff --git a/core/src/avm2/globals/flash/net/URLLoader.as b/core/src/avm2/globals/flash/net/URLLoader.as index 8f9a3ba1e314..6fe621ecc70e 100644 --- a/core/src/avm2/globals/flash/net/URLLoader.as +++ b/core/src/avm2/globals/flash/net/URLLoader.as @@ -1,35 +1,38 @@ package flash.net { - import flash.events.EventDispatcher; - import flash.net.URLRequest; - import __ruffle__.stub_method; + import flash.events.EventDispatcher; + import flash.net.URLRequest; + import __ruffle__.stub_method; - public class URLLoader extends EventDispatcher { - public var data: *; - public var dataFormat: String = "text"; + public class URLLoader extends EventDispatcher { + [Ruffle(InternalSlot)] + public var data: *; - public function URLLoader(request:URLRequest = null) { - if (request != null) { - this.load(request); - } - } + [Ruffle(InternalSlot)] + public var dataFormat: String = "text"; - // FIXME - this should be a normal property for consistency with Flash - public function get bytesTotal():uint { - if (this.data) { - return this.data.length; - } - return 0; - } + public function URLLoader(request:URLRequest = null) { + if (request != null) { + this.load(request); + } + } - // FIXME - this should be a normal property for consistency with Flash - public function get bytesLoaded():uint { - // TODO - update this as the download progresses - return this.bytesTotal - } - public native function load(request:URLRequest):void; + // FIXME - this should be a normal property for consistency with Flash + public function get bytesTotal():uint { + if (this.data) { + return this.data.length; + } + return 0; + } - public function close():void { - stub_method("flash.net.URLLoader", "close"); - } - } + // FIXME - this should be a normal property for consistency with Flash + public function get bytesLoaded():uint { + // TODO - update this as the download progresses + return this.bytesTotal + } + public native function load(request:URLRequest):void; + + public function close():void { + stub_method("flash.net.URLLoader", "close"); + } + } } diff --git a/core/src/avm2/globals/flash/net/URLRequest.as b/core/src/avm2/globals/flash/net/URLRequest.as index eea7374f5b2b..f373ffad472a 100644 --- a/core/src/avm2/globals/flash/net/URLRequest.as +++ b/core/src/avm2/globals/flash/net/URLRequest.as @@ -1,62 +1,85 @@ package flash.net { + import __ruffle__.stub_getter; + import __ruffle__.stub_setter; - import __ruffle__.stub_getter; - import __ruffle__.stub_setter; + public final class URLRequest { + // NOTE - when implementing properties (e.g. `contentType`, `data`, etc.) + // be sure to also check for them in `URLLoader` - public final class URLRequest { - // NOTE - when implementing properties (e.g. `contentType`, `data`, etc.) - // be sure to also check for them in `URLLoader` + [Ruffle(InternalSlot)] + private var _url:String; - // FIXME - this should be a getter/setter for consistency with Flash - public var url:String; - private var _contentType: String = "application/x-www-form-urlencoded"; // ignored - private var _requestHeaders: Array = []; + [Ruffle(InternalSlot)] + private var _contentType: String = "application/x-www-form-urlencoded"; // ignored - public var digest:String; - private var _method:String = URLRequestMethod.GET; - private var _data:Object; + [Ruffle(InternalSlot)] + private var _requestHeaders: Array = []; - public function URLRequest(url:String = null) { - this.url = url; - } + private var _digest:String; - public function get method():String { - return this._method; - } + [Ruffle(InternalSlot)] + private var _method:String = URLRequestMethod.GET; - public function set method(value: String):void { - // The method can apparently either be all upper or lower case, but not mixed. - if (value !== "GET" && value !== "get" && value !== "POST" && value !== "post") { - throw new ArgumentError("Error #2008: Parameter method must be one of the accepted values.", 2008); - } + [Ruffle(InternalSlot)] + private var _data:Object; - // TODO: AIR is supposed to support other methods like PUT or DELETE. - this._method = value; - } + public function URLRequest(url:String = null) { + this._url = url; + } - public function get data():Object { - return this._data; - } + public function get url():String { + return this._url; + } - public function set data(newData:Object):void { - this._data = newData; - } + public function set url(url:String):void { + this._url = url; + } - public function set contentType(value:String):void { - this._contentType = value; - } + public function get method():String { + return this._method; + } - public function get contentType():String { - return this._contentType; - } + public function set method(value: String):void { + // The method can apparently either be all upper or lower case, but not mixed. + if (value !== "GET" && value !== "get" && value !== "POST" && value !== "post") { + throw new ArgumentError("Error #2008: Parameter method must be one of the accepted values.", 2008); + } - public function get requestHeaders():Array { - return _requestHeaders; - } + // TODO: AIR is supposed to support other methods like PUT or DELETE. + this._method = value; + } - public function set requestHeaders(headers:Array):void { - _requestHeaders = headers; - } + public function get data():Object { + return this._data; + } - } + public function set data(newData:Object):void { + this._data = newData; + } + + public function set contentType(value:String):void { + this._contentType = value; + } + + public function get contentType():String { + return this._contentType; + } + + public function get requestHeaders():Array { + return this._requestHeaders; + } + + public function set requestHeaders(headers:Array):void { + this._requestHeaders = headers; + } + + public function get digest():String { + return this._digest; + } + + public function set digest(value:String):void { + this._digest = value; + } + + } } diff --git a/core/src/avm2/globals/flash/net/file_reference.rs b/core/src/avm2/globals/flash/net/file_reference.rs index 957f11502bcf..ea54b6ba7d4b 100644 --- a/core/src/avm2/globals/flash/net/file_reference.rs +++ b/core/src/avm2/globals/flash/net/file_reference.rs @@ -1,11 +1,13 @@ use crate::avm2::bytearray::ByteArrayStorage; use crate::avm2::error::{argument_error, error, make_error_2037, make_error_2097}; -pub use crate::avm2::object::file_reference_allocator; +use crate::avm2::globals::slots::flash_net_file_filter as file_filter_slots; use crate::avm2::object::{ByteArrayObject, DateObject, FileReference}; use crate::avm2::{Activation, Avm2, Error, EventObject, Object, TObject, Value}; use crate::backend::ui::FileFilter; use crate::string::AvmString; +pub use crate::avm2::object::file_reference_allocator; + pub fn get_creation_date<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, @@ -140,9 +142,9 @@ pub fn browse<'gc>( return Err(make_error_2097(activation)); } - let description = obj.get_public_property("description", activation)?; - let extension = obj.get_public_property("extension", activation)?; - let mac_type = obj.get_public_property("macType", activation)?; + let description = obj.get_slot(file_filter_slots::_DESCRIPTION); + let extension = obj.get_slot(file_filter_slots::_EXTENSION); + let mac_type = obj.get_slot(file_filter_slots::_MAC_TYPE); // The description and extension must be non-empty strings. match (description, extension) { diff --git a/core/src/avm2/globals/flash/net/shared_object.rs b/core/src/avm2/globals/flash/net/shared_object.rs index 569a36d2c14c..84aa6eaf66d1 100644 --- a/core/src/avm2/globals/flash/net/shared_object.rs +++ b/core/src/avm2/globals/flash/net/shared_object.rs @@ -253,7 +253,7 @@ pub fn clear<'gc>( let shared_object = this.as_shared_object().unwrap(); // Clear the local data object. - shared_object.reset_data(activation)?; + shared_object.reset_data(activation); // Delete data from storage backend. let name = shared_object.name(); diff --git a/core/src/avm2/globals/flash/system/LoaderContext.as b/core/src/avm2/globals/flash/system/LoaderContext.as index 0850caf35ff6..e4ae47a02fe7 100644 --- a/core/src/avm2/globals/flash/system/LoaderContext.as +++ b/core/src/avm2/globals/flash/system/LoaderContext.as @@ -3,7 +3,10 @@ package flash.system { public class LoaderContext { public var allowCodeImport : Boolean; + + [Ruffle(InternalSlot)] public var applicationDomain : ApplicationDomain; + public var checkPolicyFile : Boolean; [API("674")] public var imageDecodingPolicy : String; diff --git a/core/src/avm2/globals/flash/text/engine/ContentElement.as b/core/src/avm2/globals/flash/text/engine/ContentElement.as index 398871e0a95e..ddf7da6c672e 100644 --- a/core/src/avm2/globals/flash/text/engine/ContentElement.as +++ b/core/src/avm2/globals/flash/text/engine/ContentElement.as @@ -5,9 +5,10 @@ package flash.text.engine { public var userData; internal var _text:String = null; - + + [Ruffle(InternalSlot)] private var _elementFormat:ElementFormat; - + public function ContentElement(elementFormat:ElementFormat = null, eventMirror:EventDispatcher = null, textRotation:String = "rotate0") { // FIXME: `new ContentElement()` throws an error in Flash; see TextJustifier this._elementFormat = elementFormat; @@ -20,11 +21,11 @@ package flash.text.engine { public function get rawText():String { return this._text; } - + public function get elementFormat():ElementFormat { return this._elementFormat; } - + public function set elementFormat(value:ElementFormat):void { this._elementFormat = value; } diff --git a/core/src/avm2/globals/flash/text/engine/ElementFormat.as b/core/src/avm2/globals/flash/text/engine/ElementFormat.as index c270f77a43e8..998086a7c627 100644 --- a/core/src/avm2/globals/flash/text/engine/ElementFormat.as +++ b/core/src/avm2/globals/flash/text/engine/ElementFormat.as @@ -8,6 +8,7 @@ package flash.text.engine { private var _breakOpportunity:String; + [Ruffle(InternalSlot)] private var _color:uint; private var _digitCase:String; @@ -16,8 +17,10 @@ package flash.text.engine { private var _dominantBaseline:String; + [Ruffle(InternalSlot)] private var _fontDescription:FontDescription; + [Ruffle(InternalSlot)] private var _fontSize:Number; private var _kerning:String; diff --git a/core/src/avm2/globals/flash/text/engine/FontDescription.as b/core/src/avm2/globals/flash/text/engine/FontDescription.as index 38ae97256ff0..6e66ede81b1d 100644 --- a/core/src/avm2/globals/flash/text/engine/FontDescription.as +++ b/core/src/avm2/globals/flash/text/engine/FontDescription.as @@ -2,12 +2,16 @@ package flash.text.engine { import __ruffle__.stub_method; public final class FontDescription { + [Ruffle(InternalSlot)] private var _fontName:String; + [Ruffle(InternalSlot)] private var _fontWeight:String; + [Ruffle(InternalSlot)] private var _fontPosture:String; + [Ruffle(InternalSlot)] private var _fontLookup:String; private var _renderingMode:String; diff --git a/core/src/avm2/globals/flash/text/engine/TextBlock.as b/core/src/avm2/globals/flash/text/engine/TextBlock.as index 94323ba842c3..db18ce006492 100644 --- a/core/src/avm2/globals/flash/text/engine/TextBlock.as +++ b/core/src/avm2/globals/flash/text/engine/TextBlock.as @@ -12,10 +12,15 @@ package flash.text.engine { private var _lineRotation:String; private var _tabStops:Vector.; private var _textJustifier:TextJustifier; + + [Ruffle(InternalSlot)] private var _content:ContentElement; - internal var _textLineCreationResult:String = null; - internal var _firstLine:TextLine = null; + [Ruffle(InternalSlot)] + private var _textLineCreationResult:String = null; + + [Ruffle(InternalSlot)] + private var _firstLine:TextLine = null; public function TextBlock(content:ContentElement = null, diff --git a/core/src/avm2/globals/flash/text/engine/TextLine.as b/core/src/avm2/globals/flash/text/engine/TextLine.as index 40a7736eeadc..92a4a44f0624 100644 --- a/core/src/avm2/globals/flash/text/engine/TextLine.as +++ b/core/src/avm2/globals/flash/text/engine/TextLine.as @@ -14,9 +14,15 @@ package flash.text.engine { // to a TextLine. [Ruffle(Abstract)] public final class TextLine extends DisplayObjectContainer { - internal var _specifiedWidth:Number = 0.0; + [Ruffle(InternalSlot)] + private var _specifiedWidth:Number = 0.0; + + [Ruffle(InternalSlot)] internal var _textBlock:TextBlock = null; - internal var _rawTextLength:int = 0; + + [Ruffle(InternalSlot)] + private var _rawTextLength:int = 0; + internal var _validity:String = "valid"; public static const MAX_LINE_WIDTH:int = 1000000; diff --git a/core/src/avm2/globals/flash/text/engine/text_block.rs b/core/src/avm2/globals/flash/text/engine/text_block.rs index d01c1ff6e193..2e117e18b906 100644 --- a/core/src/avm2/globals/flash/text/engine/text_block.rs +++ b/core/src/avm2/globals/flash/text/engine/text_block.rs @@ -1,10 +1,14 @@ use crate::avm2::activation::Activation; use crate::avm2::error::Error; use crate::avm2::globals::flash::display::display_object::initialize_for_allocator; +use crate::avm2::globals::slots::flash_text_engine_content_element as element_slots; +use crate::avm2::globals::slots::flash_text_engine_element_format as format_slots; +use crate::avm2::globals::slots::flash_text_engine_font_description as font_desc_slots; +use crate::avm2::globals::slots::flash_text_engine_text_block as block_slots; +use crate::avm2::globals::slots::flash_text_engine_text_line as line_slots; use crate::avm2::object::{Object, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; -use crate::avm2::Multiname; use crate::avm2_stub_method; use crate::display_object::{EditText, TDisplayObject}; use crate::html::TextFormat; @@ -15,13 +19,12 @@ pub fn create_text_line<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let namespaces = activation.avm2().namespaces; avm2_stub_method!(activation, "flash.text.TextBlock", "createTextLine"); let previous_text_line = args.try_get_object(activation, 0); let width = args.get_f64(activation, 1)?; - let content = this.get_public_property("content", activation)?; + let content = this.get_slot(block_slots::_CONTENT); let content = if matches!(content, Value::Null) { return Ok(Value::Null); @@ -33,11 +36,8 @@ pub fn create_text_line<'gc>( Some(_) => { // Some SWFs rely on eventually getting `null` from createLineText. // TODO: Support multiple lines - this.set_property( - &Multiname::new( - namespaces.flash_text_engine_internal, - "_textLineCreationResult", - ), + this.set_slot( + block_slots::_TEXT_LINE_CREATION_RESULT, "complete".into(), activation, )?; @@ -74,47 +74,26 @@ pub fn create_text_line<'gc>( // of the provided text, and set the width of the EditText to that. // Some games depend on this (e.g. Realm Grinder). - let element_format = content - .get_public_property("elementFormat", activation)? - .as_object(); + let element_format = content.get_slot(element_slots::_ELEMENT_FORMAT).as_object(); apply_format(activation, display_object, text.as_wstr(), element_format)?; let instance = initialize_for_allocator(activation, display_object.into(), class)?; class.call_init(instance.into(), &[], activation)?; - instance.set_property( - &Multiname::new(namespaces.flash_text_engine_internal, "_textBlock"), - this.into(), - activation, - )?; + instance.set_slot(line_slots::_TEXT_BLOCK, this.into(), activation)?; - instance.set_property( - &Multiname::new(namespaces.flash_text_engine_internal, "_specifiedWidth"), - args.get_value(1), - activation, - )?; + instance.set_slot(line_slots::_SPECIFIED_WIDTH, args.get_value(1), activation)?; - instance.set_property( - &Multiname::new(namespaces.flash_text_engine_internal, "_rawTextLength"), - text.len().into(), - activation, - )?; + instance.set_slot(line_slots::_RAW_TEXT_LENGTH, text.len().into(), activation)?; - this.set_property( - &Multiname::new( - namespaces.flash_text_engine_internal, - "_textLineCreationResult", - ), + this.set_slot( + block_slots::_TEXT_LINE_CREATION_RESULT, "success".into(), activation, )?; - this.set_property( - &Multiname::new(namespaces.flash_text_engine_internal, "_firstLine"), - instance.into(), - activation, - )?; + this.set_slot(block_slots::_FIRST_LINE, instance.into(), activation)?; Ok(instance.into()) } @@ -128,37 +107,37 @@ fn apply_format<'gc>( if let Some(element_format) = element_format { // TODO: Support more ElementFormat properties let color = element_format - .get_public_property("color", activation)? + .get_slot(format_slots::_COLOR) .coerce_to_u32(activation)?; let size = element_format - .get_public_property("fontSize", activation)? + .get_slot(format_slots::_FONT_SIZE) .coerce_to_number(activation)?; let (font, bold, italic, is_device_font) = if let Value::Object(font_description) = - element_format.get_public_property("fontDescription", activation)? + element_format.get_slot(format_slots::_FONT_DESCRIPTION) { ( Some( font_description - .get_public_property("fontName", activation)? + .get_slot(font_desc_slots::_FONT_NAME) .coerce_to_string(activation)? .as_wstr() .into(), ), Some( &font_description - .get_public_property("fontWeight", activation)? + .get_slot(font_desc_slots::_FONT_WEIGHT) .coerce_to_string(activation)? == b"bold", ), Some( &font_description - .get_public_property("fontPosture", activation)? + .get_slot(font_desc_slots::_FONT_POSTURE) .coerce_to_string(activation)? == b"italic", ), &font_description - .get_public_property("fontLookup", activation)? + .get_slot(font_desc_slots::_FONT_LOOKUP) .coerce_to_string(activation)? == b"device", ) diff --git a/core/src/avm2/globals/flash/text/style_sheet.rs b/core/src/avm2/globals/flash/text/style_sheet.rs index f6f7b4ffd72e..1335e06a8c29 100644 --- a/core/src/avm2/globals/flash/text/style_sheet.rs +++ b/core/src/avm2/globals/flash/text/style_sheet.rs @@ -1,5 +1,6 @@ +use crate::avm2::object::{Object, ScriptObject, TObject}; use crate::avm2::parameters::ParametersExt; -use crate::avm2::{Activation, Error, Object, TObject, Value}; +use crate::avm2::{Activation, Error, Value}; use crate::html::{transform_dashes_to_camel_case, CssStream}; use crate::string::AvmString; use ruffle_wstr::{WStr, WString}; @@ -10,27 +11,21 @@ pub fn inner_parse_css<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { let document = args.get_string(activation, 0)?; - let result = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; + let result = ScriptObject::new_object(activation); if let Ok(css) = CssStream::new(&document).parse() { for (selector, properties) in css.into_iter() { - let object = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; + let object = ScriptObject::new_object(activation); + for (key, value) in properties.into_iter() { - object.set_public_property( + object.set_string_property_local( AvmString::new(activation.gc(), transform_dashes_to_camel_case(key)), Value::String(AvmString::new(activation.gc(), value)), activation, )?; } - result.set_public_property( + + result.set_string_property_local( AvmString::new(activation.gc(), selector), Value::Object(object), activation, diff --git a/core/src/avm2/globals/flash/text/text_format.rs b/core/src/avm2/globals/flash/text/text_format.rs index c672871b6131..f5a48f2fa521 100644 --- a/core/src/avm2/globals/flash/text/text_format.rs +++ b/core/src/avm2/globals/flash/text/text_format.rs @@ -1,6 +1,7 @@ use crate::avm2::activation::Activation; use crate::avm2::error::make_error_2008; use crate::avm2::object::{ArrayObject, Object, TObject}; +use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; use crate::avm2::Error; use crate::ecma_conversions::round_to_even; @@ -535,24 +536,22 @@ pub fn set_tab_stops<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(mut text_format) = this.as_text_format_mut() { - let value = args.get(0).unwrap_or(&Value::Undefined); + let value = args.try_get_object(activation, 0); text_format.tab_stops = match value { - Value::Null => None, - Value::Object(obj) => { - let length = obj.as_array_storage().map_or(0, |v| v.length()); + Some(obj) => { + let array_storage = obj.as_array_storage().unwrap(); + let length = array_storage.length(); let tab_stops: Result, Error<'gc>> = (0..length) .map(|i| { - let element = obj.get_public_property( - AvmString::new_utf8(activation.context.gc_context, i.to_string()), - activation, - )?; - Ok(round_to_even(element.coerce_to_number(activation)?).into()) + let value = array_storage.get(i).unwrap_or(Value::Number(0.0)); + + Ok(round_to_even(value.coerce_to_number(activation)?).into()) }) .collect(); Some(tab_stops?) } - _ => unreachable!("Array-typed argument can only be Object or Null"), + None => None, }; } diff --git a/core/src/avm2/globals/flash/ui/ContextMenu.as b/core/src/avm2/globals/flash/ui/ContextMenu.as index 7721c42a5192..d33b6a999323 100644 --- a/core/src/avm2/globals/flash/ui/ContextMenu.as +++ b/core/src/avm2/globals/flash/ui/ContextMenu.as @@ -1,18 +1,22 @@ -package flash.ui -{ +package flash.ui { import flash.display.NativeMenu; import __ruffle__.stub_getter; - public final class ContextMenu extends NativeMenu - { - public function ContextMenu() - { + public final class ContextMenu extends NativeMenu { + [Ruffle(InternalSlot)] + private var _customItems:Array; + + private var _clipboardMenu:Boolean; + + [Ruffle(InternalSlot)] + private var _builtInItems: ContextMenuBuiltInItems = new ContextMenuBuiltInItems(); + + private var _clipboardItems: ContextMenuClipboardItems = new ContextMenuClipboardItems(); + + public function ContextMenu() { super(); this.customItems = new Array(); } - - private var _customItems:Array; - private var _clipboardMenu:Boolean; public function get customItems():Array { return this._customItems; @@ -22,10 +26,18 @@ package flash.ui this._customItems = value; } - public native function hideBuiltInItems(): void; - - private var _builtInItems: ContextMenuBuiltInItems = new ContextMenuBuiltInItems(); - private var _clipboardItems: ContextMenuClipboardItems = new ContextMenuClipboardItems(); + public function hideBuiltInItems():void { + if (this._builtInItems) { + this._builtInItems.forwardAndBack = false; + this._builtInItems.loop = false; + this._builtInItems.play = false; + this._builtInItems.print = false; + this._builtInItems.quality = false; + this._builtInItems.rewind = false; + this._builtInItems.save = false; + this._builtInItems.zoom = false; + } + } public function get builtInItems(): ContextMenuBuiltInItems { return this._builtInItems; @@ -44,11 +56,11 @@ package flash.ui } public function get clipboardMenu():Boolean { - return _clipboardMenu; + return this._clipboardMenu; } public function set clipboardMenu(value:Boolean):void { - _clipboardMenu = value; + this._clipboardMenu = value; } public static function get isSupported():Boolean { diff --git a/core/src/avm2/globals/flash/ui/ContextMenuBuiltInItems.as b/core/src/avm2/globals/flash/ui/ContextMenuBuiltInItems.as index 1ab65396d131..1ef0d9749af9 100644 --- a/core/src/avm2/globals/flash/ui/ContextMenuBuiltInItems.as +++ b/core/src/avm2/globals/flash/ui/ContextMenuBuiltInItems.as @@ -1,13 +1,27 @@ -package flash.ui -{ +package flash.ui { public final class ContextMenuBuiltInItems { + [Ruffle(InternalSlot)] private var _forwardAndBack:Boolean = true; + + [Ruffle(InternalSlot)] private var _loop:Boolean = true; + + [Ruffle(InternalSlot)] private var _play:Boolean = true; + + [Ruffle(InternalSlot)] private var _print:Boolean = true; + + [Ruffle(InternalSlot)] private var _quality:Boolean = true; + + [Ruffle(InternalSlot)] private var _rewind:Boolean = true; + + [Ruffle(InternalSlot)] private var _save:Boolean = true; + + [Ruffle(InternalSlot)] private var _zoom:Boolean = true; public function get forwardAndBack():Boolean { diff --git a/core/src/avm2/globals/flash/ui/ContextMenuItem.as b/core/src/avm2/globals/flash/ui/ContextMenuItem.as index 40f29bacfb1c..e84b3b1f3ce2 100644 --- a/core/src/avm2/globals/flash/ui/ContextMenuItem.as +++ b/core/src/avm2/globals/flash/ui/ContextMenuItem.as @@ -22,8 +22,13 @@ package flash.ui return new ContextMenuItem(this.caption, this.separatorBefore, this.enabled, this.visible); } + [Ruffle(InternalSlot)] public var caption: String; + + [Ruffle(InternalSlot)] public var separatorBefore: Boolean; + + [Ruffle(InternalSlot)] public var visible: Boolean; } } diff --git a/core/src/avm2/globals/flash/ui/context_menu.rs b/core/src/avm2/globals/flash/ui/context_menu.rs index 3fccefc64ee1..6e173d486841 100644 --- a/core/src/avm2/globals/flash/ui/context_menu.rs +++ b/core/src/avm2/globals/flash/ui/context_menu.rs @@ -1,30 +1,13 @@ use crate::avm2::activation::Activation; +use crate::avm2::globals::slots::flash_display_native_menu_item as native_item_slots; +use crate::avm2::globals::slots::flash_ui_context_menu as menu_slots; +use crate::avm2::globals::slots::flash_ui_context_menu_built_in_items as builtins_slots; +use crate::avm2::globals::slots::flash_ui_context_menu_item as item_slots; use crate::avm2::object::{Object, TObject}; use crate::avm2::value::Value; -use crate::avm2::Error; use crate::context_menu; use crate::display_object::DisplayObject; -pub fn hide_built_in_items<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - if let Value::Object(items) = this.get_public_property("builtInItems", activation)? { - // items is a ContextMenuBuiltInItems - items.set_public_property("forwardAndBack", Value::Bool(false), activation)?; - items.set_public_property("loop", Value::Bool(false), activation)?; - items.set_public_property("play", Value::Bool(false), activation)?; - items.set_public_property("print", Value::Bool(false), activation)?; - items.set_public_property("quality", Value::Bool(false), activation)?; - items.set_public_property("rewind", Value::Bool(false), activation)?; - items.set_public_property("save", Value::Bool(false), activation)?; - items.set_public_property("zoom", Value::Bool(false), activation)?; - } - - Ok(Value::Undefined) -} - pub fn make_context_menu_state<'gc>( menu: Option>, object: Option>, @@ -35,36 +18,33 @@ pub fn make_context_menu_state<'gc>( result.set_display_object(object); macro_rules! check_bool { - ( $obj:expr, $name:expr, $value:expr ) => { - matches!( - $obj.get_public_property($name, activation), - Ok(Value::Bool($value)) - ) + ( $obj:expr, $slot:expr, $value:expr ) => { + matches!($obj.get_slot($slot), Value::Bool($value)) }; } let mut builtin_items = context_menu::BuiltInItemFlags::for_stage(activation.context.stage); if let Some(menu) = menu { - if let Ok(Value::Object(builtins)) = menu.get_public_property("builtInItems", activation) { - if check_bool!(builtins, "zoom", false) { + if let Some(builtins) = menu.get_slot(menu_slots::_BUILT_IN_ITEMS).as_object() { + if check_bool!(builtins, builtins_slots::_ZOOM, false) { builtin_items.zoom = false; } - if check_bool!(builtins, "quality", false) { + if check_bool!(builtins, builtins_slots::_QUALITY, false) { builtin_items.quality = false; } - if check_bool!(builtins, "play", false) { + if check_bool!(builtins, builtins_slots::_PLAY, false) { builtin_items.play = false; } - if check_bool!(builtins, "loop", false) { + if check_bool!(builtins, builtins_slots::_LOOP, false) { builtin_items.loop_ = false; } - if check_bool!(builtins, "rewind", false) { + if check_bool!(builtins, builtins_slots::_REWIND, false) { builtin_items.rewind = false; } - if check_bool!(builtins, "forwardAndBack", false) { + if check_bool!(builtins, builtins_slots::_FORWARD_AND_BACK, false) { builtin_items.forward_and_back = false; } - if check_bool!(builtins, "print", false) { + if check_bool!(builtins, builtins_slots::_PRINT, false) { builtin_items.print = false; } } @@ -73,39 +53,43 @@ pub fn make_context_menu_state<'gc>( result.build_builtin_items(builtin_items, activation.context); if let Some(menu) = menu { - if let Ok(Value::Object(custom_items)) = menu.get_public_property("customItems", activation) - { + if let Value::Object(custom_items) = menu.get_slot(menu_slots::_CUSTOM_ITEMS) { // note: this borrows the array, but it shouldn't be possible for // AS to get invoked here and cause BorrowMutError if let Some(array) = custom_items.as_array_storage() { + let context_menu_item_class = activation.avm2().class_defs().contextmenuitem; + for (i, item) in array.iter().enumerate() { // TODO: Non-CustomMenuItem Object-s shouldn't count if let Some(Value::Object(item)) = item { - let caption = if let Ok(Value::String(s)) = - item.get_public_property("caption", activation) - { - s - } else { - continue; - }; - let enabled = check_bool!(item, "enabled", true); - let visible = check_bool!(item, "visible", true); - let separator_before = check_bool!(item, "separatorBefore", true); + if item.is_of_type(context_menu_item_class) { + let caption = + if let Value::String(s) = item.get_slot(item_slots::CAPTION) { + s + } else { + continue; + }; - if !visible { - continue; - } + let enabled = check_bool!(item, native_item_slots::ENABLED, true); + let visible = check_bool!(item, item_slots::VISIBLE, true); + let separator_before = + check_bool!(item, item_slots::SEPARATOR_BEFORE, true); - result.push( - context_menu::ContextMenuItem { - enabled, - separator_before: separator_before || i == 0, - caption: caption.to_string(), - checked: false, - }, - context_menu::ContextMenuCallback::Avm2 { item }, - ); + if !visible { + continue; + } + + result.push( + context_menu::ContextMenuItem { + enabled, + separator_before: separator_before || i == 0, + caption: caption.to_string(), + checked: false, + }, + context_menu::ContextMenuCallback::Avm2 { item }, + ); + } } } } diff --git a/core/src/avm2/globals/flash/utils/Timer.as b/core/src/avm2/globals/flash/utils/Timer.as index 1713456ec365..4cefe8d6a0b5 100644 --- a/core/src/avm2/globals/flash/utils/Timer.as +++ b/core/src/avm2/globals/flash/utils/Timer.as @@ -11,6 +11,10 @@ package flash.utils { [Ruffle(InternalSlot)] private var _timerId: int = -1; + // Returns 'true' if we should cancel the underlying Ruffle native timer + [Ruffle(InternalSlot)] + private var _onUpdateClosure:Function; + private function checkDelay(delay:Number): void { if (!isFinite(delay) || delay < 0) { throw new RangeError("Timer delay out of range", 2066); @@ -22,6 +26,22 @@ package flash.utils { this._currentCount = 0; this._delay = delay; this._repeatCount = repeatCount; + + // We need this closure so we can access it by slot from native code + // (rather than making it an instance method, which would force us + // to access it as a bound method) + var self:Timer = this; + this._onUpdateClosure = function():Boolean { + self._currentCount += 1; + self.dispatchEvent(new TimerEvent(TimerEvent.TIMER, false, false)); + if (self.repeatCount != 0 && self._currentCount >= self._repeatCount) { + // This will make 'running' return false in a TIMER_COMPLETE event handler + self._timerId = -1; + self.dispatchEvent(new TimerEvent(TimerEvent.TIMER_COMPLETE, false, false)); + return true; + } + return false; + } } public function get currentCount(): int { @@ -64,18 +84,5 @@ package flash.utils { public native function stop():void; public native function start():void; - - // Returns 'true' if we should cancel the underlying Ruffle native timer - internal function onUpdate():Boolean { - this._currentCount += 1; - this.dispatchEvent(new TimerEvent(TimerEvent.TIMER, false, false)); - if (this.repeatCount != 0 && this._currentCount >= this._repeatCount) { - // This will make 'running' return false in a TIMER_COMPLETE event handler - this._timerId = -1; - this.dispatchEvent(new TimerEvent(TimerEvent.TIMER_COMPLETE, false, false)); - return true; - } - return false; - } } } diff --git a/core/src/avm2/globals/flash/utils/timer.rs b/core/src/avm2/globals/flash/utils/timer.rs index 3568787deed8..23e68bc1b6b9 100644 --- a/core/src/avm2/globals/flash/utils/timer.rs +++ b/core/src/avm2/globals/flash/utils/timer.rs @@ -4,7 +4,6 @@ use crate::avm2::activation::Activation; use crate::avm2::globals::slots::flash_utils_timer as slots; use crate::avm2::object::TObject; use crate::avm2::value::Value; -use crate::avm2::Multiname; use crate::avm2::{Error, Object}; use crate::timer::TimerCallback; @@ -30,18 +29,13 @@ pub fn start<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let namespaces = activation.avm2().namespaces; - let id = this.get_slot(slots::_TIMER_ID).coerce_to_i32(activation)?; - let delay = this.get_slot(slots::_DELAY).coerce_to_number(activation)?; + let delay = this.get_slot(slots::_DELAY).coerce_to_i32(activation)?; if id == -1 { let on_update = this - .get_property( - &Multiname::new(namespaces.flash_utils_internal, "onUpdate"), - activation, - )? + .get_slot(slots::_ON_UPDATE_CLOSURE) .as_object() .expect("Internal function is object"); @@ -53,7 +47,7 @@ pub fn start<'gc>( closure: on_update, params: vec![], }, - delay as _, + delay, false, ); this.set_slot(slots::_TIMER_ID, id.into(), activation)?; diff --git a/core/src/avm2/globals/json.rs b/core/src/avm2/globals/json.rs index ba970c2ab501..8c46b1a625ad 100644 --- a/core/src/avm2/globals/json.rs +++ b/core/src/avm2/globals/json.rs @@ -4,7 +4,7 @@ use crate::avm2::activation::Activation; use crate::avm2::array::ArrayStorage; use crate::avm2::error::{syntax_error, type_error}; use crate::avm2::globals::array::ArrayIter; -use crate::avm2::object::{ArrayObject, FunctionObject, Object, TObject}; +use crate::avm2::object::{ArrayObject, FunctionObject, Object, ScriptObject, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; use crate::avm2::Error; @@ -33,8 +33,7 @@ fn deserialize_json_inner<'gc>( } } JsonValue::Object(js_obj) => { - let obj_class = activation.avm2().classes().object; - let obj = obj_class.construct(activation, &[])?; + let obj = ScriptObject::new_object(activation); for entry in js_obj.iter() { let key = AvmString::new_utf8(activation.context.gc_context, entry.0); let val = deserialize_json_inner(activation, entry.1.clone(), reviver)?; @@ -43,9 +42,9 @@ fn deserialize_json_inner<'gc>( Some(reviver) => reviver.call(activation, Value::Null, &[key.into(), val])?, }; if matches!(mapped_val, Value::Undefined) { - obj.delete_public_property(activation, key)?; + obj.delete_string_property_local(key, activation)?; } else { - obj.set_public_property(key, mapped_val, activation)?; + obj.set_string_property_local(key, mapped_val, activation)?; } } obj.into() diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index d8ccaacd0b52..30fa7543327a 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -120,15 +120,7 @@ fn class_init<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let proto = this - .get_public_property("prototype", activation)? - .as_object() - .ok_or_else(|| { - format!( - "Specialization {} has a prototype of null or undefined", - this.instance_of_class_name(activation.context.gc_context) - ) - })?; + let proto = this.as_class_object().unwrap().prototype(); let scope = activation.create_scopechain(); const PUBLIC_PROTOTYPE_METHODS: &[(&str, NativeMethodImpl)] = &[ @@ -155,11 +147,14 @@ fn class_init<'gc>( for (pubname, func) in PUBLIC_PROTOTYPE_METHODS { proto.set_string_property_local( *pubname, - FunctionObject::from_function( + FunctionObject::from_method( activation, Method::from_builtin(*func, pubname, activation.context.gc_context), scope, - )? + None, + None, + None, + ) .into(), activation, )?; @@ -486,9 +481,8 @@ pub fn index_of<'gc>( .coerce_to_i32(activation)?; let from_index = if from_index < 0 { - let length = this - .get_public_property("length", activation)? - .coerce_to_i32(activation)?; + let length = this.as_vector_storage().unwrap().length() as i32; + max(length + from_index, 0) as u32 } else { from_index as u32 @@ -521,9 +515,8 @@ pub fn last_index_of<'gc>( .coerce_to_i32(activation)?; let from_index = if from_index < 0 { - let length = this - .get_public_property("length", activation)? - .coerce_to_i32(activation)?; + let length = this.as_vector_storage().unwrap().length() as i32; + max(length + from_index, 0) as u32 } else { from_index as u32 diff --git a/core/src/avm2/metadata.rs b/core/src/avm2/metadata.rs index d91a2beef0e6..5248408a63f5 100644 --- a/core/src/avm2/metadata.rs +++ b/core/src/avm2/metadata.rs @@ -1,12 +1,12 @@ +use crate::avm2::array::ArrayStorage; +use crate::avm2::object::{ArrayObject, Object, ScriptObject, TObject}; use crate::avm2::script::TranslationUnit; -use crate::avm2::{Activation, Error}; +use crate::avm2::{Activation, Error, Value}; use crate::string::AvmString; use gc_arena::Collect; use swf::avm2::types::{Index as AbcIndex, Metadata as AbcMetadata}; -use super::{ArrayObject, ArrayStorage, Object, TObject, Value}; - // Represents a single key-value pair for a trait metadata. #[derive(Clone, Collect, Debug, Eq, PartialEq)] #[collect(no_drop)] @@ -77,31 +77,23 @@ impl<'gc> Metadata<'gc> { &self, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let object = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; - object.set_public_property("name", self.name.into(), activation)?; + let object = ScriptObject::new_object(activation); + object.set_string_property_local("name", self.name.into(), activation)?; let values = self .items .iter() .map(|item| { - let value_object = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; - value_object.set_public_property("key", item.key.into(), activation)?; - value_object.set_public_property("value", item.value.into(), activation)?; + let value_object = ScriptObject::new_object(activation); + value_object.set_string_property_local("key", item.key.into(), activation)?; + value_object.set_string_property_local("value", item.value.into(), activation)?; Ok(Some(value_object.into())) }) .collect::>>, Error<'gc>>>()?; let values_array = ArrayObject::from_storage(activation, ArrayStorage::from_storage(values))?; - object.set_public_property("value", values_array.into(), activation)?; + object.set_string_property_local("value", values_array.into(), activation)?; Ok(object) } } diff --git a/core/src/avm2/namespace.rs b/core/src/avm2/namespace.rs index 38578fc41f1c..a6c1fb67d922 100644 --- a/core/src/avm2/namespace.rs +++ b/core/src/avm2/namespace.rs @@ -343,10 +343,6 @@ pub struct CommonNamespaces<'gc> { pub(super) vector_internal: Namespace<'gc>, pub(super) proxy: Namespace<'gc>, - // These are required to facilitate shared access between Rust and AS. - pub(super) flash_utils_internal: Namespace<'gc>, - pub(super) flash_text_engine_internal: Namespace<'gc>, - pub(super) __ruffle__: Namespace<'gc>, } @@ -371,8 +367,6 @@ impl<'gc> CommonNamespaces<'gc> { ApiVersion::AllVersions, context, ), - flash_utils_internal: Namespace::internal("flash.utils", context), - flash_text_engine_internal: Namespace::internal("flash.text.engine", context), __ruffle__: Namespace::package("__ruffle__", ApiVersion::AllVersions, context), } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index baf06176cb30..be0f89691843 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -59,6 +59,7 @@ mod shared_object_object; mod socket_object; mod sound_object; mod soundchannel_object; +mod soundtransform_object; mod stage3d_object; mod stage_object; mod textformat_object; @@ -136,6 +137,9 @@ pub use crate::avm2::object::sound_object::{ pub use crate::avm2::object::soundchannel_object::{ sound_channel_allocator, SoundChannelObject, SoundChannelObjectWeak, }; +pub use crate::avm2::object::soundtransform_object::{ + sound_transform_allocator, SoundTransformObject, SoundTransformObjectWeak, +}; pub use crate::avm2::object::stage3d_object::{ stage_3d_allocator, Stage3DObject, Stage3DObjectWeak, }; @@ -201,6 +205,7 @@ use crate::font::Font; FontObject(FontObject<'gc>), LocalConnectionObject(LocalConnectionObject<'gc>), SharedObjectObject(SharedObjectObject<'gc>), + SoundTransformObject(SoundTransformObject<'gc>), } )] pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy { @@ -229,6 +234,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy self.base().get_property_local(name, activation) } + /// Same as get_property_local, but constructs a public Multiname for you. + #[no_dynamic] + fn get_string_property_local( + self, + name: impl Into>, + activation: &mut Activation<'_, 'gc>, + ) -> Result, Error<'gc>> { + let name = Multiname::new(activation.avm2().namespaces.public_vm_internal(), name); + self.get_property_local(&name, activation) + } + /// Retrieve a property by Multiname lookup. /// /// This method should not be overridden. @@ -316,10 +332,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy base.set_property_local(name, value, activation) } - /// Same as get_property_local, but constructs a public Multiname for you. + /// Same as set_property_local, but constructs a public Multiname for you. /// TODO: this feels upside down, as in: we shouldn't need multinames/namespaces /// by the time we reach dynamic properties. /// But for now, this function is a smaller change to the core than a full refactor. + #[no_dynamic] fn set_string_property_local( self, name: impl Into>, @@ -620,6 +637,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } /// Indicates whether or not a property exists on an object. + #[no_dynamic] fn has_property(self, name: &Multiname<'gc>) -> bool { if self.has_own_property(name) { true @@ -631,6 +649,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } /// Same as has_property, but constructs a public Multiname for you. + #[no_dynamic] fn has_public_property( self, name: impl Into>, @@ -682,6 +701,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Ok(base.delete_property_local(activation.gc(), name)) } + /// Same as delete_property_local, but constructs a public Multiname for you. + #[no_dynamic] + fn delete_string_property_local( + self, + name: impl Into>, + activation: &mut Activation<'_, 'gc>, + ) -> Result> { + let name = Multiname::new(activation.avm2().namespaces.public_vm_internal(), name); + self.delete_property_local(activation, &name) + } + /// Delete a named property from the object. /// /// Returns false if the property cannot be deleted. @@ -720,17 +750,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy } } - /// Same as delete_property, but constructs a public Multiname for you. - #[no_dynamic] - fn delete_public_property( - &self, - activation: &mut Activation<'_, 'gc>, - name: impl Into>, - ) -> Result> { - let name = Multiname::new(activation.avm2().namespaces.public_all(), name); - self.delete_property(activation, &name) - } - /// Retrieve the `__proto__` of a given object. /// /// The proto is another object used to resolve methods across a class of @@ -937,32 +956,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Ok(Value::Object((*self).into())) } - /// Determine if this object is an instance of a given type. - /// - /// This uses the ES3 definition of instance, which walks the prototype - /// chain. For the ES4 definition of instance, use `is_of_type`, which uses - /// the class object chain and accounts for interfaces. - /// - /// The given object should be the class object for the given type we are - /// checking against this object. Its prototype will be extracted and - /// searched in the prototype chain of this object. - #[no_dynamic] - fn is_instance_of( - &self, - activation: &mut Activation<'_, 'gc>, - class: Object<'gc>, - ) -> Result> { - let type_proto = class - .get_public_property("prototype", activation)? - .as_object(); - - if let Some(type_proto) = type_proto { - self.has_prototype_in_chain(type_proto) - } else { - Ok(false) - } - } - /// Returns all public properties from this object's vtable, together with their values. /// This includes normal fields, const fields, and getter methods /// This is used for JSON serialization. @@ -990,26 +983,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy Ok(values) } - /// Determine if this object has a given prototype in its prototype chain. - /// - /// The given object `type_proto` should be the prototype we are checking - /// against this object. - #[no_dynamic] - fn has_prototype_in_chain(&self, type_proto: Object<'gc>) -> Result> { - let mut my_proto = self.proto(); - - //TODO: Is it a verification error to do `obj instanceof bare_object`? - while let Some(proto) = my_proto { - if Object::ptr_eq(proto, type_proto) { - return Ok(true); - } - - my_proto = proto.proto() - } - - Ok(false) - } - /// Determine if this object is an instance of a given type. /// /// This uses the ES4 definition of instance, which walks the class object @@ -1315,6 +1288,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn as_shared_object(&self) -> Option> { None } + + fn as_sound_transform(&self) -> Option> { + None + } } pub enum ObjectPtr {} @@ -1367,6 +1344,7 @@ impl<'gc> Object<'gc> { Self::FontObject(o) => WeakObject::FontObject(FontObjectWeak(Gc::downgrade(o.0))), Self::LocalConnectionObject(o) => WeakObject::LocalConnectionObject(LocalConnectionObjectWeak(Gc::downgrade(o.0))), Self::SharedObjectObject(o) => WeakObject::SharedObjectObject(SharedObjectObjectWeak(Gc::downgrade(o.0))), + Self::SoundTransformObject(o) => WeakObject::SoundTransformObject(SoundTransformObjectWeak(Gc::downgrade(o.0))), } } } @@ -1429,6 +1407,7 @@ pub enum WeakObject<'gc> { FontObject(FontObjectWeak<'gc>), LocalConnectionObject(LocalConnectionObjectWeak<'gc>), SharedObjectObject(SharedObjectObjectWeak<'gc>), + SoundTransformObject(SoundTransformObjectWeak<'gc>), } impl<'gc> WeakObject<'gc> { @@ -1474,6 +1453,7 @@ impl<'gc> WeakObject<'gc> { Self::FontObject(o) => GcWeak::as_ptr(o.0) as *const ObjectPtr, Self::LocalConnectionObject(o) => GcWeak::as_ptr(o.0) as *const ObjectPtr, Self::SharedObjectObject(o) => GcWeak::as_ptr(o.0) as *const ObjectPtr, + Self::SoundTransformObject(o) => GcWeak::as_ptr(o.0) as *const ObjectPtr, } } @@ -1519,6 +1499,7 @@ impl<'gc> WeakObject<'gc> { Self::FontObject(o) => FontObject(o.0.upgrade(mc)?).into(), Self::LocalConnectionObject(o) => LocalConnectionObject(o.0.upgrade(mc)?).into(), Self::SharedObjectObject(o) => SharedObjectObject(o.0.upgrade(mc)?).into(), + Self::SoundTransformObject(o) => SoundTransformObject(o.0.upgrade(mc)?).into(), }) } } diff --git a/core/src/avm2/object/bitmapdata_object.rs b/core/src/avm2/object/bitmapdata_object.rs index 245866f67e78..efb5476d4a9d 100644 --- a/core/src/avm2/object/bitmapdata_object.rs +++ b/core/src/avm2/object/bitmapdata_object.rs @@ -23,9 +23,7 @@ pub fn bitmap_data_allocator<'gc>( // This always starts out as a dummy (invalid) BitmapDataWrapper, so // that custom subclasses see a disposed BitmapData before they call super(). // The real BitmapDataWrapper is set by BitmapData.init() - bitmap_data: Lock::new(Some(BitmapDataWrapper::dummy( - activation.context.gc_context, - ))), + bitmap_data: Lock::new(BitmapDataWrapper::dummy(activation.gc())), }, )) .into()) @@ -54,7 +52,7 @@ pub struct BitmapDataObjectData<'gc> { /// Base script object base: ScriptObjectData<'gc>, - bitmap_data: Lock>>, + bitmap_data: Lock>, } const _: () = assert!(std::mem::offset_of!(BitmapDataObjectData, base) == 0); @@ -81,7 +79,7 @@ impl<'gc> BitmapDataObject<'gc> { activation.context.gc_context, BitmapDataObjectData { base: ScriptObjectData::new(class), - bitmap_data: Lock::new(Some(bitmap_data)), + bitmap_data: Lock::new(bitmap_data), }, )) .into(); @@ -115,12 +113,12 @@ impl<'gc> TObject<'gc> for BitmapDataObject<'gc> { } fn as_bitmap_data(&self) -> Option> { - self.0.bitmap_data.get() + Some(self.0.bitmap_data.get()) } /// Initialize the bitmap data in this object, if it's capable of /// supporting said data fn init_bitmap_data(&self, mc: &Mutation<'gc>, new_bitmap: BitmapDataWrapper<'gc>) { - unlock!(Gc::write(mc, self.0), BitmapDataObjectData, bitmap_data).set(Some(new_bitmap)); + unlock!(Gc::write(mc, self.0), BitmapDataObjectData, bitmap_data).set(new_bitmap); } } diff --git a/core/src/avm2/object/event_object.rs b/core/src/avm2/object/event_object.rs index 89e916e8699e..bfb18777f29b 100644 --- a/core/src/avm2/object/event_object.rs +++ b/core/src/avm2/object/event_object.rs @@ -259,7 +259,7 @@ impl<'gc> EventObject<'gc> { .unwrap(); for (key, value) in info { info_object - .set_public_property(key.into(), Value::String(value.into()), activation) + .set_string_property_local(key.into(), Value::String(value.into()), activation) .unwrap(); } diff --git a/core/src/avm2/object/function_object.rs b/core/src/avm2/object/function_object.rs index 030018cdd794..23cefa332b77 100644 --- a/core/src/avm2/object/function_object.rs +++ b/core/src/avm2/object/function_object.rs @@ -104,28 +104,8 @@ impl<'gc> FunctionObject<'gc> { /// Construct a function from an ABC method and the current closure scope. /// /// This associated constructor will also create and initialize an empty - /// `Object` prototype for the function. - pub fn from_function( - activation: &mut Activation<'_, 'gc>, - method: Method<'gc>, - scope: ScopeChain<'gc>, - ) -> Result, Error<'gc>> { - let this = Self::from_method(activation, method, scope, None, None, None); - let es3_proto = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; - - this.set_prototype(Some(es3_proto), activation.gc()); - - Ok(this) - } - - /// Construct a method from an ABC method and the current closure scope. - /// - /// The given `receiver`, if supplied, will override any user-specified - /// `this` parameter. + /// `Object` prototype for the function. The given `receiver`, if supplied, + /// will override any user-specified `this` parameter. pub fn from_method( activation: &mut Activation<'_, 'gc>, method: Method<'gc>, @@ -143,12 +123,14 @@ impl<'gc> FunctionObject<'gc> { bound_class, ); + let es3_proto = ScriptObject::new_object(activation); + FunctionObject(Gc::new( activation.context.gc_context, FunctionObjectData { base: ScriptObjectData::new(fn_class), exec: RefLock::new(exec), - prototype: Lock::new(None), + prototype: Lock::new(Some(es3_proto)), }, )) } diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index 17f06ae9505a..14c76b69c32a 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -86,18 +86,30 @@ fn maybe_int_property(name: AvmString<'_>) -> DynamicKey<'_> { } impl<'gc> ScriptObject<'gc> { + /// Creates an instance of the Object class, exactly as if `new Object()` + /// were called, but without going through any construction or call + /// machinery (since it's unnecessary for the Object class). + pub fn new_object(activation: &mut Activation<'_, 'gc>) -> Object<'gc> { + let object_class = activation.avm2().classes().object; + + ScriptObject(Gc::new( + activation.gc(), + ScriptObjectData::new(object_class), + )) + .into() + } + /// Construct an instance with a possibly-none class and proto chain. /// NOTE: this is a low-level function. /// This should *not* be used unless you really need /// to do something low-level, weird or lazily initialize the object. /// You shouldn't let scripts observe this weirdness. /// - /// The "everyday" way to create a normal empty ScriptObject (AS "Object") is to call - /// `avm2.classes().object.construct(self, &[])`. - /// This is equivalent to AS3 `new Object()`. + /// The proper way to create a normal empty ScriptObject (AS "Object") is to call + /// `ScriptObject::new_object(activation)`. /// - /// (calling `custom_object(mc, object_class, object_class.prototype()`) - /// is technically also equivalent and faster, but not recommended outside lower-level Core code) + /// Calling `custom_object(mc, object_class, object_class.prototype()` is + /// technically also equivalent, but not recommended outside VM initialization code pub fn custom_object( mc: &Mutation<'gc>, class: Class<'gc>, diff --git a/core/src/avm2/object/shared_object_object.rs b/core/src/avm2/object/shared_object_object.rs index 5cdc603ba901..f3a6706d8540 100644 --- a/core/src/avm2/object/shared_object_object.rs +++ b/core/src/avm2/object/shared_object_object.rs @@ -3,7 +3,7 @@ use crate::avm2::activation::Activation; use crate::avm2::error::argument_error; use crate::avm2::object::script_object::ScriptObjectData; -use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; +use crate::avm2::object::{ClassObject, Object, ObjectPtr, ScriptObject, TObject}; use crate::avm2::Error; use gc_arena::barrier::unlock; use gc_arena::{lock::Lock, Collect, Gc, GcWeak}; @@ -73,12 +73,8 @@ impl<'gc> SharedObjectObject<'gc> { self.0.data.get() } - pub fn reset_data(&self, activation: &mut Activation<'_, 'gc>) -> Result<(), Error<'gc>> { - let empty_data = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; + pub fn reset_data(&self, activation: &mut Activation<'_, 'gc>) { + let empty_data = ScriptObject::new_object(activation); unlock!( Gc::write(activation.gc(), self.0), @@ -86,8 +82,6 @@ impl<'gc> SharedObjectObject<'gc> { data ) .set(empty_data); - - Ok(()) } pub fn name(&self) -> &String { diff --git a/core/src/avm2/object/sound_object.rs b/core/src/avm2/object/sound_object.rs index 8592be238145..f3289a417960 100644 --- a/core/src/avm2/object/sound_object.rs +++ b/core/src/avm2/object/sound_object.rs @@ -1,6 +1,7 @@ //! Object representation for sounds use crate::avm2::activation::Activation; +use crate::avm2::globals::slots::flash_media_id3info as id3_slots; use crate::avm2::object::script_object::ScriptObjectData; use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; use crate::avm2::Avm2; @@ -180,60 +181,60 @@ impl<'gc> SoundObject<'gc> { let tag = Tag::read_from2(Cursor::new(bytes)); if let Ok(ref tag) = tag { if let Some(v) = tag.album() { - id3.set_public_property( - "album", + id3.set_slot( + id3_slots::ALBUM, AvmString::new_utf8(activation.gc(), v).into(), activation, ) - .expect("failed set_public_property"); + .expect("slot is typed String"); } if let Some(v) = tag.artist() { - id3.set_public_property( - "artist", + id3.set_slot( + id3_slots::ARTIST, AvmString::new_utf8(activation.gc(), v).into(), activation, ) - .expect("failed set_public_property"); + .expect("slot is typed String"); } if let Some(v) = tag.comments().next() { - id3.set_public_property( - "comment", + id3.set_slot( + id3_slots::COMMENT, AvmString::new_utf8(activation.gc(), v.text.clone()).into(), activation, ) - .expect("failed set_public_property"); + .expect("slot is typed String"); } if let Some(v) = tag.genre() { - id3.set_public_property( - "genre", + id3.set_slot( + id3_slots::GENRE, AvmString::new_utf8(activation.gc(), v).into(), activation, ) - .expect("failed set_public_property"); + .expect("slot is typed String"); } if let Some(v) = tag.title() { - id3.set_public_property( - "songName", + id3.set_slot( + id3_slots::SONG_NAME, AvmString::new_utf8(activation.gc(), v).into(), activation, ) - .expect("failed set_public_property"); + .expect("slot is typed String"); } if let Some(v) = tag.track() { - id3.set_public_property( - "track", + id3.set_slot( + id3_slots::TRACK, AvmString::new_utf8(activation.gc(), v.to_string()).into(), activation, ) - .expect("failed set_public_property"); + .expect("slot is typed String"); } if let Some(v) = tag.year() { - id3.set_public_property( - "year", + id3.set_slot( + id3_slots::YEAR, AvmString::new_utf8(activation.gc(), v.to_string()).into(), activation, ) - .expect("failed set_public_property"); + .expect("slot is typed String"); } } self.set_id3(activation.context.gc_context, Some(id3)); diff --git a/core/src/avm2/object/soundtransform_object.rs b/core/src/avm2/object/soundtransform_object.rs new file mode 100644 index 000000000000..060c313fde8b --- /dev/null +++ b/core/src/avm2/object/soundtransform_object.rs @@ -0,0 +1,124 @@ +use crate::avm2::activation::Activation; +use crate::avm2::object::script_object::ScriptObjectData; +use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; +use crate::avm2::Error; +use core::fmt; +use gc_arena::{Collect, Gc, GcWeak}; +use std::cell::Cell; + +/// A class instance allocator that allocates SoundTransform objects. +pub fn sound_transform_allocator<'gc>( + class: ClassObject<'gc>, + activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + let base = ScriptObjectData::new(class); + + Ok(SoundTransformObject(Gc::new( + activation.context.gc_context, + SoundTransformObjectData { + base, + left_to_left: Cell::new(0.0), + left_to_right: Cell::new(0.0), + right_to_left: Cell::new(0.0), + right_to_right: Cell::new(0.0), + volume: Cell::new(0.0), + }, + )) + .into()) +} + +#[derive(Clone, Collect, Copy)] +#[collect(no_drop)] +pub struct SoundTransformObject<'gc>(pub Gc<'gc, SoundTransformObjectData<'gc>>); + +#[derive(Clone, Collect, Copy, Debug)] +#[collect(no_drop)] +pub struct SoundTransformObjectWeak<'gc>(pub GcWeak<'gc, SoundTransformObjectData<'gc>>); + +impl fmt::Debug for SoundTransformObject<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SoundTransformObject") + .field("ptr", &Gc::as_ptr(self.0)) + .finish() + } +} + +#[derive(Collect)] +#[collect(no_drop)] +#[repr(C, align(8))] +pub struct SoundTransformObjectData<'gc> { + /// Base script object + base: ScriptObjectData<'gc>, + + left_to_left: Cell, + left_to_right: Cell, + right_to_left: Cell, + right_to_right: Cell, + + volume: Cell, +} + +const _: () = assert!(std::mem::offset_of!(SoundTransformObjectData, base) == 0); +const _: () = assert!( + std::mem::align_of::() == std::mem::align_of::() +); + +impl SoundTransformObject<'_> { + pub fn left_to_left(self) -> f64 { + self.0.left_to_left.get() + } + + pub fn set_left_to_left(self, value: f64) { + self.0.left_to_left.set(value); + } + + pub fn left_to_right(self) -> f64 { + self.0.left_to_right.get() + } + + pub fn set_left_to_right(self, value: f64) { + self.0.left_to_right.set(value); + } + + pub fn right_to_left(self) -> f64 { + self.0.right_to_left.get() + } + + pub fn set_right_to_left(self, value: f64) { + self.0.right_to_left.set(value); + } + + pub fn right_to_right(self) -> f64 { + self.0.right_to_right.get() + } + + pub fn set_right_to_right(self, value: f64) { + self.0.right_to_right.set(value); + } + + pub fn volume(self) -> f64 { + self.0.volume.get() + } + + pub fn set_volume(self, value: f64) { + self.0.volume.set(value); + } +} + +impl<'gc> TObject<'gc> for SoundTransformObject<'gc> { + fn gc_base(&self) -> Gc<'gc, ScriptObjectData<'gc>> { + // SAFETY: Object data is repr(C), and a compile-time assert ensures + // that the ScriptObjectData stays at offset 0 of the struct- so the + // layouts are compatible + + unsafe { Gc::cast(self.0) } + } + + fn as_ptr(&self) -> *const ObjectPtr { + Gc::as_ptr(self.0) as *const ObjectPtr + } + + fn as_sound_transform(&self) -> Option> { + Some(*self) + } +} diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 64f62b8b9b96..e1ad9f759551 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -1103,9 +1103,9 @@ impl<'gc> Value<'gc> { } } - /// Get the class that this value is of, supporting primitives. - /// This function will panic if passed Value::Null or Value::Undefined to; - /// make sure to handle them with `null_check` or similar beforehand. + /// Get the class that this Value is of. + /// + /// This function will panic if called on null or undefined. pub fn instance_class(&self, activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let class_defs = activation.avm2().class_defs(); @@ -1121,12 +1121,67 @@ impl<'gc> Value<'gc> { } } + /// Get the prototype object corresponding to this Value's type. + /// + /// This function will panic if called on null or undefined. + pub fn proto(&self, activation: &mut Activation<'_, 'gc>) -> Option> { + let classes = activation.avm2().classes(); + + match self { + Value::Bool(_) => Some(classes.boolean.prototype()), + Value::Number(_) | Value::Integer(_) => Some(classes.number.prototype()), + Value::String(_) => Some(classes.string.prototype()), + Value::Object(obj) => obj.proto(), + + Value::Undefined | Value::Null => { + unreachable!("Should not have Undefined or Null in `proto`") + } + } + } + pub fn instance_of_class_name(&self, activation: &mut Activation<'_, 'gc>) -> AvmString<'gc> { self.instance_class(activation) .name() .to_qualified_name(activation.gc()) } + /// Determine if this value is an instance of a given type. + /// + /// This uses the ES3 definition of instance, which walks the prototype + /// chain. For the ES4 definition of instance, use `is_of_type`, which uses + /// the class object chain and accounts for interfaces. + /// + /// The given object should be the class object for the given type we are + /// checking against this object. Its prototype will be extracted and + /// searched in the prototype chain of this object. + /// + /// This function will panic if called on null or undefined. + pub fn is_instance_of( + &self, + activation: &mut Activation<'_, 'gc>, + class_or_function_object: Object<'gc>, + ) -> bool { + let type_proto = match class_or_function_object { + Object::ClassObject(class_object) => Some(class_object.prototype()), + Object::FunctionObject(function_object) => function_object.prototype(), + _ => panic!("Object must be either ClassObject or FunctionObject"), + }; + + if let Some(type_proto) = type_proto { + let mut my_proto = self.proto(activation); + + while let Some(proto) = my_proto { + if Object::ptr_eq(proto, type_proto) { + return true; + } + + my_proto = proto.proto(); + } + } + + false + } + /// Implements the strict-equality `===` check for AVM2. pub fn strict_eq(&self, other: &Value<'gc>) -> bool { if self == other { diff --git a/core/src/display_object.rs b/core/src/display_object.rs index 71fbae196ccb..4de0cb2b2073 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -2723,32 +2723,18 @@ impl SoundTransform { self.right_to_left = 0; } - pub fn from_avm2_object<'gc>( - activation: &mut Avm2Activation<'_, 'gc>, - as3_st: Avm2Object<'gc>, - ) -> Result> { - Ok(SoundTransform { - left_to_left: (as3_st - .get_public_property("leftToLeft", activation)? - .coerce_to_number(activation)? - * 100.0) as i32, - left_to_right: (as3_st - .get_public_property("leftToRight", activation)? - .coerce_to_number(activation)? - * 100.0) as i32, - right_to_left: (as3_st - .get_public_property("rightToLeft", activation)? - .coerce_to_number(activation)? - * 100.0) as i32, - right_to_right: (as3_st - .get_public_property("rightToRight", activation)? - .coerce_to_number(activation)? - * 100.0) as i32, - volume: (as3_st - .get_public_property("volume", activation)? - .coerce_to_number(activation)? - * 100.0) as i32, - }) + pub fn from_avm2_object(as3_st: Avm2Object<'_>) -> Self { + let sound_transform = as3_st + .as_sound_transform() + .expect("Should pass SoundTransform"); + + SoundTransform { + left_to_left: (sound_transform.left_to_left() * 100.0) as i32, + left_to_right: (sound_transform.left_to_right() * 100.0) as i32, + right_to_left: (sound_transform.right_to_left() * 100.0) as i32, + right_to_right: (sound_transform.right_to_right() * 100.0) as i32, + volume: (sound_transform.volume() * 100.0) as i32, + } } pub fn into_avm2_object<'gc>( @@ -2759,31 +2745,17 @@ impl SoundTransform { .avm2() .classes() .soundtransform - .construct(activation, &[])?; - - as3_st.set_public_property( - "leftToLeft", - (self.left_to_left as f64 / 100.0).into(), - activation, - )?; - as3_st.set_public_property( - "leftToRight", - (self.left_to_right as f64 / 100.0).into(), - activation, - )?; - as3_st.set_public_property( - "rightToLeft", - (self.right_to_left as f64 / 100.0).into(), - activation, - )?; - as3_st.set_public_property( - "rightToRight", - (self.right_to_right as f64 / 100.0).into(), - activation, - )?; - as3_st.set_public_property("volume", (self.volume as f64 / 100.0).into(), activation)?; - - Ok(as3_st) + .construct(activation, &[])? + .as_sound_transform() + .unwrap(); + + as3_st.set_left_to_left(self.left_to_left as f64 / 100.0); + as3_st.set_left_to_right(self.left_to_right as f64 / 100.0); + as3_st.set_right_to_left(self.right_to_left as f64 / 100.0); + as3_st.set_right_to_right(self.right_to_right as f64 / 100.0); + as3_st.set_volume(self.volume as f64 / 100.0); + + Ok(as3_st.into()) } } diff --git a/core/src/external.rs b/core/src/external.rs index 3088b2989118..60ae66a14f33 100644 --- a/core/src/external.rs +++ b/core/src/external.rs @@ -261,7 +261,8 @@ impl Value { for (key, value) in values.into_iter() { let key = AvmString::new_utf8(activation.context.gc_context, key); let value = value.into_avm2(activation); - obj.set_public_property(key, value, activation).unwrap(); + obj.set_string_property_local(key, value, activation) + .unwrap(); } Avm2Value::Object(obj) } diff --git a/core/src/loader.rs b/core/src/loader.rs index adab15cde280..7cb977ecf286 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -7,13 +7,14 @@ use crate::avm1::{Object, TObject, Value}; use crate::avm2::bytearray::ByteArrayStorage; use crate::avm2::globals::flash::utils::byte_array::strip_bom; use crate::avm2::object::{ - ByteArrayObject, EventObject as Avm2EventObject, FileReferenceObject, LoaderStream, - TObject as _, + ByteArrayObject, EventObject as Avm2EventObject, FileReferenceObject, LoaderInfoObject, + LoaderStream, TObject as _, }; use crate::avm2::{ Activation as Avm2Activation, Avm2, BitmapDataObject, Domain as Avm2Domain, Object as Avm2Object, }; +use crate::avm2_stub_method_context; use crate::backend::navigator::{ErrorResponse, OwnedFuture, Request, SuccessResponse}; use crate::backend::ui::DialogResultFuture; use crate::bitmap::bitmap_data::Color; @@ -30,7 +31,6 @@ use crate::streams::NetStream; use crate::string::AvmString; use crate::tag_utils::SwfMovie; use crate::vminterface::Instantiator; -use crate::{avm2_stub_method, avm2_stub_method_context}; use chardetng::EncodingDetector; use encoding_rs::{UTF_8, WINDOWS_1252}; use gc_arena::{Collect, GcCell}; @@ -745,7 +745,7 @@ pub enum MovieLoaderVMData<'gc> { broadcaster: Option>, }, Avm2 { - loader_info: Avm2Object<'gc>, + loader_info: LoaderInfoObject<'gc>, /// The context of the SWF being loaded. context: Option>, @@ -1570,9 +1570,10 @@ impl<'gc> Loader<'gc> { activation: &mut Avm2Activation<'a, 'gc>, target: Avm2Object<'gc>, ) { + use crate::avm2::globals::slots::flash_net_url_loader as url_loader_slots; + let data_format = target - .get_public_property("dataFormat", activation) - .expect("The dataFormat field exists on URLLoaders") + .get_slot(url_loader_slots::DATA_FORMAT) .coerce_to_string(activation) .expect("The dataFormat field is typed String"); @@ -1605,7 +1606,7 @@ impl<'gc> Loader<'gc> { if let Some(data_object) = data_object { target - .set_public_property("data", data_object, activation) + .set_slot(url_loader_slots::DATA, data_object, activation) .unwrap(); } } @@ -2016,7 +2017,7 @@ impl<'gc> Loader<'gc> { let activation = Avm2Activation::from_nothing(uc); let open_evt = Avm2EventObject::bare_default_event(activation.context, "open"); - Avm2::dispatch_event(uc, open_evt, loader_info); + Avm2::dispatch_event(uc, open_evt, loader_info.into()); } } @@ -2062,12 +2063,11 @@ impl<'gc> Loader<'gc> { .. } = vm_data { + use crate::avm2::globals::slots::flash_system_loader_context as loader_context_slots; + let domain = context - .and_then(|o| { - o.get_public_property("applicationDomain", &mut activation) - .ok() - }) - .and_then(|v| v.coerce_to_object(&mut activation).ok()) + .map(|o| o.get_slot(loader_context_slots::APPLICATION_DOMAIN)) + .and_then(|v| v.as_object()) .and_then(|o| o.as_application_domain()) .unwrap_or_else(|| { let parent_domain = default_domain; @@ -2103,10 +2103,7 @@ impl<'gc> Loader<'gc> { }; if let MovieLoaderVMData::Avm2 { loader_info, .. } = vm_data { - loader_info - .as_loader_info_object() - .unwrap() - .set_content_type(sniffed_type); + loader_info.set_content_type(sniffed_type); let fake_movie = Arc::new(SwfMovie::fake_with_compressed_len( activation.context.swf.version(), data.len(), @@ -2115,13 +2112,10 @@ impl<'gc> Loader<'gc> { // Expose 'bytesTotal' (via the fake movie) during the first 'progress' event, // but nothing else (in particular, the `parameters` and `url` properties are not set // to their real values) - loader_info - .as_loader_info_object() - .unwrap() - .set_loader_stream( - LoaderStream::NotYetLoaded(fake_movie, Some(clip), false), - activation.context.gc_context, - ); + loader_info.set_loader_stream( + LoaderStream::NotYetLoaded(fake_movie, Some(clip), false), + activation.context.gc_context, + ); // Flash always fires an initial 'progress' event with // bytesLoaded=0 and bytesTotal set to the proper value. @@ -2131,13 +2125,10 @@ impl<'gc> Loader<'gc> { // Update the LoaderStream - we now have a real SWF movie and a real target clip // This is intentionally set *after* the first 'progress' event, to match Flash's behavior // (`LoaderInfo.parameters` is always empty during the first 'progress' event) - loader_info - .as_loader_info_object() - .unwrap() - .set_loader_stream( - LoaderStream::NotYetLoaded(movie.clone(), Some(clip), false), - activation.context.gc_context, - ); + loader_info.set_loader_stream( + LoaderStream::NotYetLoaded(movie.clone(), Some(clip), false), + activation.context.gc_context, + ); } match sniffed_type { @@ -2151,7 +2142,7 @@ impl<'gc> Loader<'gc> { if let Some(mc) = clip.as_movie_clip() { let loader_info = if let MovieLoaderVMData::Avm2 { loader_info, .. } = vm_data { - Some(*loader_info.as_loader_info_object().unwrap()) + Some(loader_info) } else { None }; @@ -2252,13 +2243,10 @@ impl<'gc> Loader<'gc> { data.len(), )); - loader_info - .as_loader_info_object() - .unwrap() - .set_loader_stream( - LoaderStream::NotYetLoaded(fake_movie, Some(bitmap_dobj), false), - activation.context.gc_context, - ); + loader_info.set_loader_stream( + LoaderStream::NotYetLoaded(fake_movie, Some(bitmap_dobj), false), + activation.context.gc_context, + ); } Loader::movie_loader_progress(handle, activation.context, length, length)?; @@ -2268,9 +2256,8 @@ impl<'gc> Loader<'gc> { activation.context.swf.version(), data.to_vec(), )); - let loader_info_obj = loader_info.as_loader_info_object().unwrap(); - loader_info_obj.set_loader_stream( + loader_info.set_loader_stream( LoaderStream::NotYetLoaded(fake_movie, Some(bitmap_dobj), false), activation.context.gc_context, ); @@ -2331,7 +2318,6 @@ impl<'gc> Loader<'gc> { data.len(), )); - let loader_info = loader_info.as_loader_info_object().unwrap(); loader_info.set_errored(true); loader_info.set_loader_stream( @@ -2423,7 +2409,7 @@ impl<'gc> Loader<'gc> { ) .map_err(|e| Error::Avm2Error(e.to_string()))?; - Avm2::dispatch_event(uc, progress_evt, loader_info); + Avm2::dispatch_event(uc, progress_evt, loader_info.into()); } } @@ -2450,7 +2436,7 @@ impl<'gc> Loader<'gc> { }; let loader_info = if let MovieLoaderVMData::Avm2 { loader_info, .. } = vm_data { - Some(*loader_info.as_loader_info_object().unwrap()) + Some(loader_info) } else { None }; @@ -2499,17 +2485,9 @@ impl<'gc> Loader<'gc> { } if let MovieLoaderVMData::Avm2 { loader_info, .. } = vm_data { - let domain = uc - .library - .library_for_movie(movie.clone().unwrap()) - .unwrap() - .avm2_domain(); - let mut activation = Avm2Activation::from_domain(uc, domain); let mut loader = loader_info - .get_public_property("loader", &mut activation) - .map_err(|e| Error::Avm2Error(e.to_string()))? - .as_object() - .unwrap() + .loader() + .expect("Loader should be Some") .as_display_object() .unwrap() .as_container() @@ -2518,16 +2496,14 @@ impl<'gc> Loader<'gc> { // This isn't completely correct - the 'large_preload' test observes the child // being set after an 'enterFrame' call. However, our current logic should // hopefully be good enough. - avm2_stub_method!( - activation, + avm2_stub_method_context!( + uc, "flash.display.Loader", "load", "addChild at the correct time" ); - if let Some(loader_info) = loader_info.as_loader_info_object() { - loader_info.set_expose_content(); - } + loader_info.set_expose_content(); // Note that we do *not* use the 'addChild' method here: // Per the flash docs, our implementation always throws @@ -2536,14 +2512,10 @@ impl<'gc> Loader<'gc> { // frame constructor will see an 'added' event immediately, and // an 'addedToStage' event *after* the constructor finishes // when we add the movie as a child of the loader. - loader.insert_at_index(activation.context, dobj.unwrap(), 0); + loader.insert_at_index(uc, dobj.unwrap(), 0); if !movie.unwrap().is_action_script_3() { - loader.insert_child_into_depth_list( - activation.context, - LOADER_INSERTED_AVM1_DEPTH, - dobj.unwrap(), - ); + loader.insert_child_into_depth_list(uc, LOADER_INSERTED_AVM1_DEPTH, dobj.unwrap()); } } else if let Some(dobj) = dobj { // This is a load of an image into AVM1 - add it as a child of the target clip. @@ -2574,16 +2546,15 @@ impl<'gc> Loader<'gc> { // This is fired after we process the movie's first frame, // in `MovieClip.on_exit_frame` MovieLoaderVMData::Avm2 { loader_info, .. } => { - let loader_info_obj = loader_info.as_loader_info_object().unwrap(); - let current_movie = { loader_info_obj.as_loader_stream().unwrap().movie().clone() }; - loader_info_obj.set_loader_stream( + let current_movie = { loader_info.as_loader_stream().unwrap().movie().clone() }; + loader_info.set_loader_stream( LoaderStream::Swf(current_movie, dobj.unwrap()), uc.gc_context, ); if let Some(dobj) = dobj { if dobj.as_movie_clip().is_none() { - loader_info_obj.fire_init_and_complete_events(uc, status, redirected); + loader_info.fire_init_and_complete_events(uc, status, redirected); } } } @@ -2664,7 +2635,7 @@ impl<'gc> Loader<'gc> { ) .map_err(|e| Error::Avm2Error(e.to_string()))?; - Avm2::dispatch_event(activation.context, http_status_evt, loader_info); + Avm2::dispatch_event(activation.context, http_status_evt, loader_info.into()); // FIXME - Match the exact error message generated by Flash @@ -2682,7 +2653,7 @@ impl<'gc> Loader<'gc> { ) .map_err(|e| Error::Avm2Error(e.to_string()))?; - Avm2::dispatch_event(uc, io_error_evt, loader_info); + Avm2::dispatch_event(uc, io_error_evt, loader_info.into()); } } diff --git a/tests/tests/swfs/avm2/function_proto/Test.as b/tests/tests/swfs/avm2/function_proto/Test.as index c155ab77520d..3984af696f7f 100644 --- a/tests/tests/swfs/avm2/function_proto/Test.as +++ b/tests/tests/swfs/avm2/function_proto/Test.as @@ -17,3 +17,4 @@ trace(f()) var p = Function.prototype; trace(p()); trace(Function.prototype.call.call()); +trace(uint.prototype.toString.prototype); diff --git a/tests/tests/swfs/avm2/function_proto/output.txt b/tests/tests/swfs/avm2/function_proto/output.txt index 191224fa731a..9cc631063962 100644 --- a/tests/tests/swfs/avm2/function_proto/output.txt +++ b/tests/tests/swfs/avm2/function_proto/output.txt @@ -2,3 +2,4 @@ function Function() {} undefined undefined undefined +[object Object] diff --git a/tests/tests/swfs/avm2/function_proto/test.swf b/tests/tests/swfs/avm2/function_proto/test.swf index 3c35ff08db74..4324269513d2 100644 Binary files a/tests/tests/swfs/avm2/function_proto/test.swf and b/tests/tests/swfs/avm2/function_proto/test.swf differ diff --git a/tests/tests/swfs/avm2/int_instanceof/output.txt b/tests/tests/swfs/avm2/int_instanceof/output.txt new file mode 100644 index 000000000000..dac32ecf1d7b --- /dev/null +++ b/tests/tests/swfs/avm2/int_instanceof/output.txt @@ -0,0 +1,3 @@ +false +true +true diff --git a/tests/tests/swfs/avm2/int_instanceof/test.swf b/tests/tests/swfs/avm2/int_instanceof/test.swf new file mode 100644 index 000000000000..5669b1800857 Binary files /dev/null and b/tests/tests/swfs/avm2/int_instanceof/test.swf differ diff --git a/tests/tests/swfs/avm2/int_instanceof/test.toml b/tests/tests/swfs/avm2/int_instanceof/test.toml new file mode 100644 index 000000000000..dbee897f5863 --- /dev/null +++ b/tests/tests/swfs/avm2/int_instanceof/test.toml @@ -0,0 +1 @@ +num_frames = 1