From 9a3e8da5d276b1e0ea614db8870b73453d1d191b Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 6 Oct 2024 08:54:12 -0700 Subject: [PATCH 01/10] avm2: Work towards starting to remove PrimitiveObject --- core/src/avm2/activation.rs | 46 ++++++---- core/src/avm2/globals.rs | 18 +++- core/src/avm2/globals/avmplus.rs | 127 ++++++--------------------- core/src/avm2/globals/flash/utils.as | 30 ++++--- core/src/avm2/globals/flash/utils.rs | 33 +++---- core/src/avm2/globals/null.rs | 30 +++++++ core/src/avm2/globals/void.rs | 3 +- core/src/avm2/value.rs | 77 ++++++++++++---- 8 files changed, 190 insertions(+), 174 deletions(-) create mode 100644 core/src/avm2/globals/null.rs diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 069863a2aeb8..a2bd9f80242e 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -761,21 +761,17 @@ impl<'a, 'gc> Activation<'a, 'gc> { let verified_info = method.verified_info.borrow(); let exception_list = &verified_info.as_ref().unwrap().exceptions; - // Use `coerce_to_object` so that we handle primitives correctly. - let err_object = error.coerce_to_object(self); let last_ip = self.ip - 1; for e in exception_list { if last_ip >= e.from_offset as i32 && last_ip < e.to_offset as i32 { - let mut matches = false; - // A typeless catch block (e.g. `catch(er) { ... }`) will - // always match. - if e.target_class.is_none() { - matches = true; - } else if let Ok(err_object) = err_object { - let target_class = e.target_class.expect("Just confirmed to be non-None"); - - matches = err_object.is_of_type(target_class); - } + let matches = if let Some(target_class) = e.target_class { + // This ensures null and undefined don't match + error.is_of_type(self, target_class) + } else { + // A typeless catch block (i.e. `catch(err:*) { ... }`) will + // always match. + true + }; if matches { #[cfg(feature = "avm_debug")] @@ -1719,7 +1715,11 @@ impl<'a, 'gc> Activation<'a, 'gc> { } fn op_get_slot(&mut self, index: u32) -> Result, Error<'gc>> { - let object = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; + let object = self + .pop_stack() + .null_check(self, None)? + .as_object() + .expect("Cannot get_slot on primitive"); let value = object.get_slot(index); self.push_stack(value); @@ -1729,7 +1729,11 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_set_slot(&mut self, index: u32) -> Result, Error<'gc>> { let value = self.pop_stack(); - let object = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; + let object = self + .pop_stack() + .null_check(self, None)? + .as_object() + .expect("Cannot set_slot on primitive"); object.set_slot(index, value, self)?; @@ -1738,7 +1742,11 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_set_slot_no_coerce(&mut self, index: u32) -> Result, Error<'gc>> { let value = self.pop_stack(); - let object = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; + let object = self + .pop_stack() + .null_check(self, None)? + .as_object() + .expect("Cannot set_slot on primitive"); object.set_slot_no_coerce(index, value, self.context.gc_context); @@ -1994,16 +2002,18 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn op_check_filter(&mut self) -> Result, Error<'gc>> { let xml = self.avm2().class_defs().xml; let xml_list = self.avm2().class_defs().xml_list; - let value = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; + let value = self.pop_stack().null_check(self, None)?; - if value.is_of_type(xml) || value.is_of_type(xml_list) { + if value.is_of_type(self, xml) || value.is_of_type(self, xml_list) { self.push_stack(value); } else { + let class_name = value.instance_of_class_name(self); + return Err(Error::AvmError(type_error( self, &format!( "Error #1123: Filter operator not supported on type {}.", - value.instance_of_class_name(self.context.gc_context) + class_name ), 1123, )?)); diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index dcc601f60b0c..916a17fcfbfa 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -31,6 +31,7 @@ mod int; mod json; mod math; mod namespace; +mod null; mod number; mod object; mod q_name; @@ -177,6 +178,7 @@ pub struct SystemClassDefs<'gc> { pub object: Class<'gc>, pub class: Class<'gc>, pub function: Class<'gc>, + pub null: Class<'gc>, pub void: Class<'gc>, pub array: Class<'gc>, @@ -330,11 +332,18 @@ impl<'gc> SystemClasses<'gc> { } impl<'gc> SystemClassDefs<'gc> { - fn new(object: Class<'gc>, class: Class<'gc>, function: Class<'gc>, void: Class<'gc>) -> Self { + fn new( + object: Class<'gc>, + class: Class<'gc>, + function: Class<'gc>, + null: Class<'gc>, + void: Class<'gc>, + ) -> Self { SystemClassDefs { object, class, function, + null, void, // temporary initialization @@ -531,10 +540,14 @@ pub fn load_player_globals<'gc>( // Function is more of a "normal" class than the other two, so we can create it normally. let fn_classdef = function::create_class(activation, object_i_class, class_i_class); + // This is a weird internal class in avmplus, but it allows for implementing + // `describeType(null)` in a cleaner way + let null_def = null::create_class(activation); + // void doesn't have a ClassObject let void_def = void::create_class(activation); - // Register the classes in the domain, now (except for the global class) + // Register the classes in the domain, now (except for the global and null classes) domain.export_class(object_i_class.name(), object_i_class, mc); domain.export_class(class_i_class.name(), class_i_class, mc); domain.export_class(fn_classdef.name(), fn_classdef, mc); @@ -544,6 +557,7 @@ pub fn load_player_globals<'gc>( object_i_class, class_i_class, fn_classdef, + null_def, void_def, )); diff --git a/core/src/avm2/globals/avmplus.rs b/core/src/avm2/globals/avmplus.rs index 154273fa48e7..0a7e9a7d13fb 100644 --- a/core/src/avm2/globals/avmplus.rs +++ b/core/src/avm2/globals/avmplus.rs @@ -19,12 +19,9 @@ pub fn describe_type_json<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { let flags = DescribeTypeFlags::from_bits(args.get_u32(activation, 1)?).expect("Invalid flags!"); - if args[0] == Value::Null { - return describe_type_json_null(activation, flags); - } - let value = args[0].coerce_to_object(activation)?; - let class_def = value.instance_class(); + let value = args[0]; + let class_def = instance_class_describe_type(activation, value); let object = activation .avm2() .classes() @@ -54,7 +51,11 @@ pub fn describe_type_json<'gc>( object.set_public_property("isFinal", used_class_def.is_final().into(), activation)?; object.set_public_property( "isStatic", - value.as_class_object().is_some().into(), + value + .as_object() + .and_then(|o| o.as_class_object()) + .is_some() + .into(), activation, )?; @@ -85,102 +86,6 @@ bitflags::bitflags! { } } -fn describe_type_json_null<'gc>( - activation: &mut Activation<'_, 'gc>, - flags: DescribeTypeFlags, -) -> Result, Error<'gc>> { - if flags.contains(DescribeTypeFlags::USE_ITRAITS) { - return Ok(Value::Null); - } - let object = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; - - object.set_public_property("name", "null".into(), activation)?; - object.set_public_property("isDynamic", false.into(), activation)?; - object.set_public_property("isFinal", true.into(), activation)?; - object.set_public_property("isStatic", false.into(), activation)?; - - let traits = activation - .avm2() - .classes() - .object - .construct(activation, &[])?; - - if flags.contains(DescribeTypeFlags::INCLUDE_TRAITS) { - traits.set_public_property( - "bases", - if flags.contains(DescribeTypeFlags::INCLUDE_BASES) { - ArrayObject::empty(activation)?.into() - } else { - Value::Null - }, - activation, - )?; - traits.set_public_property( - "interfaces", - if flags.contains(DescribeTypeFlags::INCLUDE_INTERFACES) { - ArrayObject::empty(activation)?.into() - } else { - Value::Null - }, - activation, - )?; - traits.set_public_property( - "variables", - if flags.contains(DescribeTypeFlags::INCLUDE_VARIABLES) { - ArrayObject::empty(activation)?.into() - } else { - Value::Null - }, - activation, - )?; - traits.set_public_property( - "accessors", - if flags.contains(DescribeTypeFlags::INCLUDE_ACCESSORS) { - ArrayObject::empty(activation)?.into() - } else { - Value::Null - }, - activation, - )?; - traits.set_public_property( - "methods", - if flags.contains(DescribeTypeFlags::INCLUDE_METHODS) { - ArrayObject::empty(activation)?.into() - } else { - Value::Null - }, - activation, - )?; - traits.set_public_property( - "metadata", - if flags.contains(DescribeTypeFlags::INCLUDE_METADATA) { - ArrayObject::empty(activation)?.into() - } else { - Value::Null - }, - activation, - )?; - traits.set_public_property( - "constructor", - if flags.contains(DescribeTypeFlags::INCLUDE_CONSTRUCTOR) { - ArrayObject::empty(activation)?.into() - } else { - Value::Null - }, - activation, - )?; - object.set_public_property("traits", traits.into(), activation)?; - } else { - object.set_public_property("traits", Value::Null, activation)?; - } - - Ok(object.into()) -} - fn describe_internal_body<'gc>( activation: &mut Activation<'_, 'gc>, class_def: Class<'gc>, @@ -573,3 +478,21 @@ fn write_metadata<'gc>( } Ok(()) } + +/// Like `Value::instance_class`, but supports Value::Null and Value::Undefined, +/// and returns `int` for Value::Integer instead of `Number`. +/// +/// Used for `describeType`, `getQualifiedClassName`, and `getQualifiedSuperClassName`. +pub fn instance_class_describe_type<'gc>( + activation: &mut Activation<'_, 'gc>, + value: Value<'gc>, +) -> Class<'gc> { + let class_defs = activation.avm2().class_defs(); + + match value { + Value::Null => class_defs.null, + Value::Undefined => class_defs.void, + Value::Integer(_) => class_defs.int, + _ => value.instance_class(activation), + } +} diff --git a/core/src/avm2/globals/flash/utils.as b/core/src/avm2/globals/flash/utils.as index fa43e6a8a57f..0cbd1e0c6d1c 100644 --- a/core/src/avm2/globals/flash/utils.as +++ b/core/src/avm2/globals/flash/utils.as @@ -1,18 +1,22 @@ package flash.utils { - public native function getDefinitionByName(name:String):Object; - public native function getQualifiedClassName(value:*):String; - public native function getQualifiedSuperclassName(value:*):String; - public native function getTimer():int; + public native function getDefinitionByName(name:String):Object; + public native function getQualifiedClassName(value:*):String; + public native function getQualifiedSuperclassName(value:*):String; + public native function getTimer():int; - public function describeType(value:*): XML { - return avmplus.describeType(value, avmplus.FLASH10_FLAGS); - } + public function describeType(value:*): XML { + if (value === undefined) { + throw new TypeError("Error #1010: A term is undefined and has no properties.", 1010); + } - public native function setInterval(closure:Function, delay:Number, ... arguments):uint; - public native function clearInterval(id:uint):void; - public native function setTimeout(closure:Function, delay:Number, ... arguments):uint; - public native function clearTimeout(id:uint):void; - public native function escapeMultiByte(s:String):String; - public native function unescapeMultiByte(s:String):String; + return avmplus.describeType(value, avmplus.FLASH10_FLAGS); + } + + public native function setInterval(closure:Function, delay:Number, ... arguments):uint; + public native function clearInterval(id:uint):void; + public native function setTimeout(closure:Function, delay:Number, ... arguments):uint; + public native function clearTimeout(id:uint):void; + public native function escapeMultiByte(s:String):String; + public native function unescapeMultiByte(s:String):String; } diff --git a/core/src/avm2/globals/flash/utils.rs b/core/src/avm2/globals/flash/utils.rs index 74a1d5e3aedf..bad1ce6464f2 100644 --- a/core/src/avm2/globals/flash/utils.rs +++ b/core/src/avm2/globals/flash/utils.rs @@ -1,6 +1,9 @@ //! `flash.utils` namespace +use crate::avm2::globals::avmplus::instance_class_describe_type; + use crate::avm2::object::TObject; +use crate::avm2::parameters::ParametersExt; use crate::avm2::{Activation, Error, Object, Value}; use crate::string::AvmString; use crate::string::WString; @@ -189,24 +192,11 @@ pub fn get_qualified_class_name<'gc>( _this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - // This is a native method, which enforces the argument count. - let val = args[0]; - match val { - Value::Null => return Ok("null".into()), - Value::Undefined => return Ok("void".into()), - _ => {} - } - let obj = val.coerce_to_object(activation)?; - - let class = match obj.as_class_object() { - Some(class) => class.inner_class_definition(), - None => obj.instance_class(), - }; + let value = args.get_value(0); + let class = instance_class_describe_type(activation, value); - Ok(class - .name() - .to_qualified_name(activation.context.gc_context) - .into()) + let mc = activation.gc(); + Ok(class.dollar_removed_name(mc).to_qualified_name(mc).into()) } /// Implements `flash.utils.getQualifiedSuperclassName` @@ -215,14 +205,11 @@ pub fn get_qualified_superclass_name<'gc>( _this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let obj = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; + let value = args.get_value(0); - let class = match obj.as_class_object() { + let class = match value.as_object().and_then(|o| o.as_class_object()) { Some(class) => class.inner_class_definition(), - None => obj.instance_class(), + None => instance_class_describe_type(activation, value), }; if let Some(super_class) = class.super_class() { diff --git a/core/src/avm2/globals/null.rs b/core/src/avm2/globals/null.rs new file mode 100644 index 000000000000..da44697167a7 --- /dev/null +++ b/core/src/avm2/globals/null.rs @@ -0,0 +1,30 @@ +use crate::avm2::activation::Activation; +use crate::avm2::class::{Class, ClassAttributes}; +use crate::avm2::method::Method; +use crate::avm2::object::Object; +use crate::avm2::value::Value; +use crate::avm2::Error; +use crate::avm2::QName; + +fn null_init<'gc>( + _activation: &mut Activation<'_, 'gc>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + unreachable!() +} + +pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { + let mc = activation.context.gc_context; + let class = Class::custom_new( + QName::new(activation.avm2().namespaces.public_all(), "null"), + None, + Method::from_builtin(null_init, "", mc), + mc, + ); + class.set_attributes(mc, ClassAttributes::FINAL | ClassAttributes::SEALED); + + class.mark_traits_loaded(activation.context.gc_context); + + class +} diff --git a/core/src/avm2/globals/void.rs b/core/src/avm2/globals/void.rs index c715a8039aaa..8c196eac8e5e 100644 --- a/core/src/avm2/globals/void.rs +++ b/core/src/avm2/globals/void.rs @@ -1,5 +1,5 @@ use crate::avm2::activation::Activation; -use crate::avm2::class::Class; +use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::method::Method; use crate::avm2::object::Object; use crate::avm2::value::Value; @@ -22,6 +22,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { Method::from_builtin(void_init, "", mc), mc, ); + class.set_attributes(mc, ClassAttributes::FINAL | ClassAttributes::SEALED); class.mark_traits_loaded(activation.context.gc_context); diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 735588e915db..9640e433cd51 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -883,6 +883,19 @@ impl<'gc> Value<'gc> { self.coerce_to_object(activation) } + #[inline(always)] + pub fn null_check( + &self, + activation: &mut Activation<'_, 'gc>, + name: Option<&Multiname<'gc>>, + ) -> Result, Error<'gc>> { + if matches!(self, Value::Null | Value::Undefined) { + return Err(error::make_null_or_undefined_error(activation, *self, name)); + } + + Ok(*self) + } + pub fn as_object(&self) -> Option> { match self { Value::Object(o) => Some(*o), @@ -993,7 +1006,11 @@ impl<'gc> Value<'gc> { return Ok(self.coerce_to_string(activation)?.into()); } - if let Ok(object) = self.coerce_to_object(activation) { + if class == activation.avm2().class_defs().object { + return Ok(*self); + } + + if let Some(object) = self.as_object() { if object.is_of_type(class) { return Ok(*self); } @@ -1064,34 +1081,64 @@ impl<'gc> Value<'gc> { /// considered instances of all numeric types that can represent them. For /// example, 5 is simultaneously an instance of `int`, `uint`, and /// `Number`. - pub fn is_of_type( - &self, - activation: &mut Activation<'_, 'gc>, - type_object: Class<'gc>, - ) -> bool { - if type_object == activation.avm2().class_defs().number { + pub fn is_of_type(&self, activation: &mut Activation<'_, 'gc>, type_class: Class<'gc>) -> bool { + if type_class == activation.avm2().class_defs().number { return self.is_number(); } - if type_object == activation.avm2().class_defs().uint { + if type_class == activation.avm2().class_defs().uint { return self.is_u32(); } - if type_object == activation.avm2().class_defs().int { + if type_class == activation.avm2().class_defs().int { return self.is_i32(); } - if let Value::Undefined = self { - if type_object == activation.avm2().class_defs().void { - return true; - } + if type_class == activation.avm2().class_defs().void { + return matches!(self, Value::Undefined); } - if let Ok(o) = self.coerce_to_object(activation) { - o.is_of_type(type_object) + if type_class == activation.avm2().class_defs().boolean { + return matches!(self, Value::Bool(_)); + } + + if type_class == activation.avm2().class_defs().string { + return matches!(self, Value::String(_)); + } + + if type_class == activation.avm2().class_defs().object { + return !matches!(self, Value::Undefined | Value::Null); + } + + if let Some(o) = self.as_object() { + o.is_of_type(type_class) } else { false } } + /// 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. + pub fn instance_class(&self, activation: &mut Activation<'_, 'gc>) -> Class<'gc> { + let class_defs = activation.avm2().class_defs(); + + match self { + Value::Bool(_) => class_defs.boolean, + Value::Number(_) | Value::Integer(_) => class_defs.number, + Value::String(_) => class_defs.string, + Value::Object(obj) => obj.instance_class(), + + Value::Undefined | Value::Null => { + unreachable!("Should not have Undefined or Null in `instance_class`") + } + } + } + + pub fn instance_of_class_name(&self, activation: &mut Activation<'_, 'gc>) -> AvmString<'gc> { + self.instance_class(activation) + .name() + .to_qualified_name(activation.gc()) + } + /// Implements the strict-equality `===` check for AVM2. pub fn strict_eq(&self, other: &Value<'gc>) -> bool { if self == other { From 692e2d6293a4988fb1e2cf854dc5a5524893bfea Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 6 Oct 2024 10:47:56 -0700 Subject: [PATCH 02/10] tests: Test `void` and `null` classes --- tests/tests/swfs/avm2/null_void_types/output.txt | 8 ++++++++ tests/tests/swfs/avm2/null_void_types/test.swf | Bin 0 -> 977 bytes tests/tests/swfs/avm2/null_void_types/test.toml | 1 + 3 files changed, 9 insertions(+) create mode 100644 tests/tests/swfs/avm2/null_void_types/output.txt create mode 100644 tests/tests/swfs/avm2/null_void_types/test.swf create mode 100644 tests/tests/swfs/avm2/null_void_types/test.toml diff --git a/tests/tests/swfs/avm2/null_void_types/output.txt b/tests/tests/swfs/avm2/null_void_types/output.txt new file mode 100644 index 000000000000..51f138ce78d9 --- /dev/null +++ b/tests/tests/swfs/avm2/null_void_types/output.txt @@ -0,0 +1,8 @@ + + +null +void +null +null + +TypeError: Error #1010: A term is undefined and has no properties. diff --git a/tests/tests/swfs/avm2/null_void_types/test.swf b/tests/tests/swfs/avm2/null_void_types/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..99d444b38af7890e1abad277032b11360d0d46f5 GIT binary patch literal 977 zcmV;?11|hSS5pz=1pokeoPAVXQ`<%qy}SBYt!!f(i~;jygHuv)u|nFkp(#@e5o9I_ z({ZNFbcV)~yu|9*vPW9eq|+yULLd69KhWRM$G$>3Z6!W{yyOIilw1Y;wy7%n4 zM`y1-UV!opAR7WqBiPMr0I-8cqtUO0UlfRs$L#y^Zg2aG9mv5tV1ayEX>;D+tk-0zKxhTS3*ppDm#!T;I~1w%cj>%u_qed(PW_HP-_#C5Mh`@Y+CxqHkepELgDK`Dq=;NmZoq?QZmJA@n&(o_+jaz(w)-BrB6zCOPdnG zNRecfWW+U{8O!GKQMO31Sdj@<2qpw;1f>W{(-a~oPw+S`AUcWY5+d3%(QXjBLg@Ph zuM)gQ^bd$$C%QrOTSVU=soOM#X#vwoOqVdB%QA>h1cR)o8bT641xElNRG>0oBq0!} zn2?Mh0uWs}H5fFp(VQ`w(?)a3Xcmp;q|q#h^C`?HM;q-45hbOk{>G=q5X}G#Q3;7@ z7FUO8PP#Ef^YVK`w4kgE(V}{Dh|1({<>Ync#F52N#87Ma#35BUd7VoEP@B|fD1e`& z-(O7*Mo#2$Qc;mDnnkCrCCW5bo_VXR*xI3FE3c|JG$nj6iz3Ot5=mP-n1iU! z)}pG7w^iyjyRp?M7kKg9;ctoDbsd Date: Sun, 6 Oct 2024 13:43:52 -0700 Subject: [PATCH 03/10] avm2: Remove a lot of uses of `coerce_to_object` --- core/src/avm2/globals/flash/display/bitmap.rs | 14 +++--- .../avm2/globals/flash/display/bitmap_data.rs | 13 ++--- .../globals/flash/display/display_object.rs | 18 +++---- .../avm2/globals/flash/display/graphics.rs | 6 +-- core/src/avm2/globals/flash/display/loader.rs | 47 +++++++++-------- .../globals/flash/display3D/context_3d.rs | 50 +++++++------------ .../globals/flash/display3D/program_3d.rs | 22 +++----- core/src/avm2/globals/flash/geom/transform.rs | 5 +- .../avm2/globals/flash/media/sound_channel.rs | 7 +-- .../avm2/globals/flash/media/sound_mixer.rs | 15 ++---- core/src/avm2/globals/flash/net.rs | 16 +++--- .../avm2/globals/flash/text/text_format.rs | 10 ++-- .../avm2/globals/flash/utils/byte_array.rs | 36 ++++--------- core/src/avm2/globals/flash/utils/timer.rs | 4 +- 14 files changed, 108 insertions(+), 155 deletions(-) diff --git a/core/src/avm2/globals/flash/display/bitmap.rs b/core/src/avm2/globals/flash/display/bitmap.rs index 158ecab77cd0..29c0b1647062 100644 --- a/core/src/avm2/globals/flash/display/bitmap.rs +++ b/core/src/avm2/globals/flash/display/bitmap.rs @@ -135,15 +135,15 @@ pub fn set_bitmap_data<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(bitmap) = this.as_display_object().and_then(|dobj| dobj.as_bitmap()) { - let bitmap_data = args.get(0).unwrap_or(&Value::Null); - let bitmap_data = if matches!(bitmap_data, Value::Null) { - BitmapDataWrapper::dummy(activation.context.gc_context) + let bitmap_data = args.try_get_object(activation, 0); + + let bitmap_data = if let Some(bitmap_data) = bitmap_data { + bitmap_data.as_bitmap_data().expect("Must be a BitmapData") } else { - bitmap_data - .coerce_to_object(activation)? - .as_bitmap_data() - .ok_or_else(|| Error::RustError("Argument was not a BitmapData".into()))? + // Passing null results in a dummy BitmapData being set. + BitmapDataWrapper::dummy(activation.gc()) }; + bitmap.set_bitmap_data(activation.context, bitmap_data); } diff --git a/core/src/avm2/globals/flash/display/bitmap_data.rs b/core/src/avm2/globals/flash/display/bitmap_data.rs index c9540f07bd08..72e63f8f3eb6 100644 --- a/core/src/avm2/globals/flash/display/bitmap_data.rs +++ b/core/src/avm2/globals/flash/display/bitmap_data.rs @@ -469,17 +469,14 @@ pub fn set_pixels<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { let rectangle = args.get_object(activation, 0, "rect")?; + let bytearray = args.get_object(activation, 0, "inputByteArray")?; - let bytearray = args - .get(1) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; if let Some(bitmap_data) = this.as_bitmap_data() { let (x, y, width, height) = get_rectangle_x_y_width_height(activation, rectangle)?; let mut ba_write = bytearray .as_bytearray_mut() - .ok_or("ArgumentError: Parameter must be a bytearray")?; + .expect("Parameter must be a bytearray"); operations::set_pixels_from_byte_array( activation.context.gc_context, @@ -559,13 +556,9 @@ pub fn copy_channel<'gc>( ) -> Result, Error<'gc>> { if let Some(bitmap_data) = this.as_bitmap_data() { bitmap_data.check_valid(activation)?; - let source_bitmap = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; + let source_bitmap = args.get_object(activation, 0, "sourceBitmapData")?; let source_rect = args.get_object(activation, 1, "sourceRect")?; - let dest_point = args.get_object(activation, 2, "destPoint")?; let dest_x = dest_point diff --git a/core/src/avm2/globals/flash/display/display_object.rs b/core/src/avm2/globals/flash/display/display_object.rs index f1847e4b603d..dd630d0f9644 100644 --- a/core/src/avm2/globals/flash/display/display_object.rs +++ b/core/src/avm2/globals/flash/display/display_object.rs @@ -308,17 +308,14 @@ pub fn set_filters<'gc>( let mut filter_vec = Vec::with_capacity(filters_storage.length()); for filter in filters_storage.iter().flatten() { - if matches!(filter, Value::Undefined | Value::Null) { + if !filter.is_of_type(activation, filter_class) { return build_argument_type_error(activation); - } else { - let filter_object = filter.coerce_to_object(activation)?; - - if !filter_object.is_of_type(filter_class) { - return build_argument_type_error(activation); - } - - filter_vec.push(Filter::from_avm2_object(activation, filter_object)?); } + + let filter_object = filter + .as_object() + .expect("BitmapFilter value should be Object"); + filter_vec.push(Filter::from_avm2_object(activation, filter_object)?); } dobj.set_filters(activation.context.gc_context, filter_vec); @@ -774,7 +771,8 @@ pub fn set_transform<'gc>( .coerce_to_object(activation)?; let color_transform = transform .get_public_property("colorTransform", activation)? - .coerce_to_object(activation)?; + .as_object() + .expect("colorTransform should be non-null"); let matrix = crate::avm2::globals::flash::geom::transform::object_to_matrix(matrix, activation)?; diff --git a/core/src/avm2/globals/flash/display/graphics.rs b/core/src/avm2/globals/flash/display/graphics.rs index 4787d5038520..d2added76178 100644 --- a/core/src/avm2/globals/flash/display/graphics.rs +++ b/core/src/avm2/globals/flash/display/graphics.rs @@ -1209,9 +1209,9 @@ pub fn draw_graphics_data<'gc>( if let Some(mut drawing) = this.as_drawing(activation.context.gc_context) { for elem in vector.iter() { - let obj = elem.coerce_to_object(activation)?; - - handle_igraphics_data(activation, &mut drawing, &obj)?; + if let Some(obj) = elem.as_object() { + handle_igraphics_data(activation, &mut drawing, &obj)?; + } } }; } diff --git a/core/src/avm2/globals/flash/display/loader.rs b/core/src/avm2/globals/flash/display/loader.rs index 62ac3b18327e..0e65b6a902d8 100644 --- a/core/src/avm2/globals/flash/display/loader.rs +++ b/core/src/avm2/globals/flash/display/loader.rs @@ -147,30 +147,33 @@ pub fn request_from_url_request<'gc>( let headers = url_request .get_public_property("requestHeaders", activation)? - .coerce_to_object(activation)? - .as_array_object() - .unwrap(); + .as_object(); - let headers = headers.as_array_storage().unwrap(); let mut string_headers = IndexMap::default(); - for i in 0..headers.length() { - let Some(header) = headers.get(i).and_then(|val| val.as_object()) else { - continue; - }; - - let name = header - .get_public_property("name", activation)? - .coerce_to_string(activation)? - .to_string(); - let value = header - .get_public_property("value", activation)? - .coerce_to_string(activation)? - .to_string(); - - // Note - testing with Flash Player shows that later entries in the array - // overwrite earlier ones with the same name. Flash Player never sends an HTTP - // request with duplicate headers - string_headers.insert(name, value); + if let Some(headers) = headers { + let headers = headers.as_array_object().unwrap(); + + let headers = headers.as_array_storage().unwrap(); + + for i in 0..headers.length() { + let Some(header) = headers.get(i).and_then(|val| val.as_object()) else { + continue; + }; + + let name = header + .get_public_property("name", activation)? + .coerce_to_string(activation)? + .to_string(); + let value = header + .get_public_property("value", activation)? + .coerce_to_string(activation)? + .to_string(); + + // Note - testing with Flash Player shows that later entries in the array + // overwrite earlier ones with the same name. Flash Player never sends an HTTP + // request with duplicate headers + string_headers.insert(name, value); + } } let method = diff --git a/core/src/avm2/globals/flash/display3D/context_3d.rs b/core/src/avm2/globals/flash/display3D/context_3d.rs index 016e8c99e2f2..77fb83ff85bd 100644 --- a/core/src/avm2/globals/flash/display3D/context_3d.rs +++ b/core/src/avm2/globals/flash/display3D/context_3d.rs @@ -131,9 +131,9 @@ pub fn set_vertex_buffer_at<'gc>( ) -> Result, Error<'gc>> { if let Some(context) = this.as_context_3d() { let index = args.get_u32(activation, 0)?; - let buffer = if matches!(args[1], Value::Null) { - None - } else { + let buffer = args.try_get_object(activation, 1); + + let buffer = if let Some(buffer) = buffer { // Note - we only check the format string if the buffer is non-null let format = args.get_string(activation, 3)?; @@ -155,14 +155,9 @@ pub fn set_vertex_buffer_at<'gc>( )?)); }; - Some(( - args.get(1) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)? - .as_vertex_buffer() - .unwrap(), - format, - )) + Some((buffer.as_vertex_buffer().unwrap(), format)) + } else { + None }; let buffer_offset = args.get_u32(activation, 2)?; @@ -204,9 +199,7 @@ pub fn draw_triangles<'gc>( ) -> Result, Error<'gc>> { if let Some(context) = this.as_context_3d() { let index_buffer = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)? + .get_object(activation, 0, "indexBuffer")? .as_index_buffer() .unwrap(); @@ -291,12 +284,9 @@ pub fn set_program_constants_from_matrix<'gc>( let first_register = args.get_u32(activation, 1)?; - let mut matrix = args - .get(2) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; + let mut matrix = args.get_object(activation, 2, "matrix")?; - let user_transposedMatrix = args.get(3).unwrap_or(&Value::Undefined).coerce_to_boolean(); + let user_transposed_matrix = args.get_bool(3); // Hack - we store in column-major form, but we need it in row-major form // So, do the *opposite* of what the user pasess in` @@ -305,7 +295,7 @@ pub fn set_program_constants_from_matrix<'gc>( // It seems like the documentation is wrong - we really copy to the registers // in column-major order. // See https://github.com/openfl/openfl/blob/971a4c9e43b5472fd84d73920a2b7c1b3d8d9257/src/openfl/display3D/Context3D.hx#L1532-L1550 - if user_transposedMatrix { + if user_transposed_matrix { matrix = matrix .call_public_property("clone", &[], activation)? .coerce_to_object(activation)?; @@ -346,11 +336,7 @@ pub fn set_program_constants_from_vector<'gc>( let first_register = args.get_u32(activation, 1)?; - let vector = args - .get(2) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; - + let vector = args.get_object(activation, 2, "vector")?; let vector = vector.as_vector_storage().unwrap(); let num_registers = args.get_i32(activation, 3)?; @@ -503,19 +489,21 @@ pub fn set_texture_at<'gc>( // This is a native method, so all of the arguments have been checked and coerced for us let sampler = args[0].as_i32() as u32; let mut cube = false; - let texture = if matches!(args[1], Value::Null) { - None - } else { - let obj = args[1].coerce_to_object(activation)?; - cube = obj.is_of_type( + let texture_object = args.try_get_object(activation, 1); + let texture = if let Some(texture_object) = texture_object { + cube = texture_object.is_of_type( activation .avm2() .classes() .cubetexture .inner_class_definition(), ); - Some(obj.as_texture().unwrap().handle()) + + Some(texture_object.as_texture().unwrap().handle()) + } else { + None }; + context.set_texture_at(sampler, texture, cube); } Ok(Value::Undefined) diff --git a/core/src/avm2/globals/flash/display3D/program_3d.rs b/core/src/avm2/globals/flash/display3D/program_3d.rs index 199a96a86b8c..69754bf11ffc 100644 --- a/core/src/avm2/globals/flash/display3D/program_3d.rs +++ b/core/src/avm2/globals/flash/display3D/program_3d.rs @@ -1,7 +1,7 @@ -use crate::avm2::Activation; - -use crate::avm2::TObject; -use crate::avm2::Value; +use crate::avm2::activation::Activation; +use crate::avm2::object::TObject; +use crate::avm2::parameters::ParametersExt; +use crate::avm2::value::Value; use crate::avm2::{Error, Object}; pub fn upload<'gc>( @@ -10,22 +10,16 @@ pub fn upload<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(this) = this.as_program_3d() { - let vertex_agal = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; + let vertex_agal = args.get_object(activation, 0, "source_vertex")?; let vertex_agal = vertex_agal .as_bytearray() - .ok_or_else(|| Error::from("ArgumentError: Parameter must be a ByteArray"))?; + .expect("Parameter must be a ByteArray"); let vertex_agal = vertex_agal.bytes().to_vec(); - let fragment_agal = args - .get(1) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; + let fragment_agal = args.get_object(activation, 1, "source_fragment")?; let fragment_agal = fragment_agal .as_bytearray() - .ok_or_else(|| Error::from("ArgumentError: Parameter must be a ByteArray"))?; + .expect("Parameter must be a ByteArray"); let fragment_agal = fragment_agal.bytes().to_vec(); this.context3d() diff --git a/core/src/avm2/globals/flash/geom/transform.rs b/core/src/avm2/globals/flash/geom/transform.rs index b311896165ff..a18bdfe30c9b 100644 --- a/core/src/avm2/globals/flash/geom/transform.rs +++ b/core/src/avm2/globals/flash/geom/transform.rs @@ -54,7 +54,10 @@ pub fn set_color_transform<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let ct = object_to_color_transform(args.get_object(activation, 0, "value")?, activation)?; + let ct = object_to_color_transform( + args.get_object(activation, 0, "colorTransform")?, + activation, + )?; let dobj = get_display_object(this, activation)?; dobj.set_color_transform(activation.context.gc_context, ct); if let Some(parent) = dobj.parent() { diff --git a/core/src/avm2/globals/flash/media/sound_channel.rs b/core/src/avm2/globals/flash/media/sound_channel.rs index b499afa08406..8a2ce0eb1543 100644 --- a/core/src/avm2/globals/flash/media/sound_channel.rs +++ b/core/src/avm2/globals/flash/media/sound_channel.rs @@ -2,6 +2,7 @@ use crate::avm2::activation::Activation; use crate::avm2::object::{Object, TObject}; +use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; use crate::avm2::Error; use crate::display_object::SoundTransform; @@ -78,11 +79,7 @@ pub fn set_sound_transform<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(sound_channel) = this.as_sound_channel() { - let as3_st = args - .get(0) - .cloned() - .unwrap_or(Value::Undefined) - .coerce_to_object(activation)?; + let as3_st = args.get_object(activation, 0, "soundChannel")?; let dobj_st = SoundTransform::from_avm2_object(activation, 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 94ab03007240..cbdc77b58646 100644 --- a/core/src/avm2/globals/flash/media/sound_mixer.rs +++ b/core/src/avm2/globals/flash/media/sound_mixer.rs @@ -3,6 +3,7 @@ use crate::avm2::activation::Activation; use crate::avm2::object::Object; use crate::avm2::object::TObject; +use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2_stub_getter; @@ -32,11 +33,7 @@ pub fn set_sound_transform<'gc>( _this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let as3_st = args - .get(0) - .cloned() - .unwrap_or(Value::Undefined) - .coerce_to_object(activation)?; + let as3_st = args.get_object(activation, 0, "sndTransform")?; let dobj_st = SoundTransform::from_avm2_object(activation, as3_st)?; activation.context.set_global_sound_transform(dobj_st); @@ -70,11 +67,7 @@ pub fn set_buffer_time<'gc>( _this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let buffer_time = args - .get(0) - .cloned() - .unwrap_or(Value::Undefined) - .coerce_to_i32(activation)?; + let buffer_time = args.get_i32(activation, 0)?; activation .context @@ -104,7 +97,7 @@ pub fn compute_spectrum<'gc>( _this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let arg0 = args[0].as_object().unwrap(); + let arg0 = args.get_object(activation, 0, "sound")?; let mut bytearray = arg0.as_bytearray_mut().unwrap(); let mut hist = activation.context.audio.get_sample_history(); diff --git a/core/src/avm2/globals/flash/net.rs b/core/src/avm2/globals/flash/net.rs index 102b6578e75a..8f100c5c945d 100644 --- a/core/src/avm2/globals/flash/net.rs +++ b/core/src/avm2/globals/flash/net.rs @@ -53,8 +53,11 @@ fn parse_data<'gc>( .classes() .urlvariables .inner_class_definition(); + if data.is_of_type(activation, urlvariables) { - let obj = data.coerce_to_object(activation)?; + let obj = data + .as_object() + .expect("URLVariables object should be Value::Object"); vars = object_to_index_map(activation, &obj).unwrap_or_default(); } else if *data != Value::Null { let str_data = data.coerce_to_string(activation)?.to_string(); @@ -63,6 +66,7 @@ fn parse_data<'gc>( } url.push_str(&str_data); } + Ok((url, vars)) } @@ -72,15 +76,9 @@ pub fn navigate_to_url<'gc>( _this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let request = args - .get(0) - .ok_or("navigateToURL: not enough arguments")? - .coerce_to_object(activation)?; + let request = args.get_object(activation, 0, "request")?; - let target = args - .get(1) - .ok_or("navigateToURL: not enough arguments")? - .coerce_to_string(activation)?; + let target = args.get_string(activation, 1)?; match request.get_public_property("url", activation)? { Value::Null => Err(make_error_2007(activation, "url")), diff --git a/core/src/avm2/globals/flash/text/text_format.rs b/core/src/avm2/globals/flash/text/text_format.rs index 120c91edfe15..c672871b6131 100644 --- a/core/src/avm2/globals/flash/text/text_format.rs +++ b/core/src/avm2/globals/flash/text/text_format.rs @@ -537,14 +537,13 @@ pub fn set_tab_stops<'gc>( if let Some(mut text_format) = this.as_text_format_mut() { let value = args.get(0).unwrap_or(&Value::Undefined); text_format.tab_stops = match value { - Value::Undefined | Value::Null => None, - value => { - let object = value.coerce_to_object(activation)?; - let length = object.as_array_storage().map_or(0, |v| v.length()); + Value::Null => None, + Value::Object(obj) => { + let length = obj.as_array_storage().map_or(0, |v| v.length()); let tab_stops: Result, Error<'gc>> = (0..length) .map(|i| { - let element = object.get_public_property( + let element = obj.get_public_property( AvmString::new_utf8(activation.context.gc_context, i.to_string()), activation, )?; @@ -553,6 +552,7 @@ pub fn set_tab_stops<'gc>( .collect(); Some(tab_stops?) } + _ => unreachable!("Array-typed argument can only be Object or Null"), }; } diff --git a/core/src/avm2/globals/flash/utils/byte_array.rs b/core/src/avm2/globals/flash/utils/byte_array.rs index 56d8e448fabe..ea16516b53f1 100644 --- a/core/src/avm2/globals/flash/utils/byte_array.rs +++ b/core/src/avm2/globals/flash/utils/byte_array.rs @@ -5,6 +5,7 @@ use crate::avm2::bytearray::{Endian, ObjectEncoding}; use crate::avm2::error::make_error_2008; pub use crate::avm2::object::byte_array_allocator; use crate::avm2::object::{Object, TObject}; +use crate::avm2::parameters::ParametersExt; use crate::avm2::value::Value; use crate::avm2::Error; use crate::string::AvmString; @@ -41,25 +42,17 @@ pub fn write_bytes<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let bytearray = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; - let offset = args - .get(1) - .unwrap_or(&Value::Integer(0)) - .coerce_to_u32(activation)? as usize; - let length = args - .get(2) - .unwrap_or(&Value::Integer(0)) - .coerce_to_u32(activation)? as usize; + let bytearray = args.get_object(activation, 0, "bytes")?; + let offset = args.get_u32(activation, 1)? as usize; + let length = args.get_u32(activation, 2)? as usize; + if !Object::ptr_eq(this, bytearray) { // The ByteArray we are reading from is different than the ByteArray we are writing to, // so we are allowed to borrow both at the same time without worrying about a panic let ba_read = bytearray .as_bytearray() - .ok_or("ArgumentError: Parameter must be a bytearray")?; + .expect("Parameter must be a bytearray"); let to_write = ba_read .read_at( // If length is 0, lets read the remaining bytes of ByteArray from the supplied offset @@ -99,18 +92,9 @@ pub fn read_bytes<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let bytearray = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; - let offset = args - .get(1) - .unwrap_or(&Value::Integer(0)) - .coerce_to_u32(activation)? as usize; - let length = args - .get(2) - .unwrap_or(&Value::Integer(0)) - .coerce_to_u32(activation)? as usize; + let bytearray = args.get_object(activation, 0, "bytes")?; + let offset = args.get_u32(activation, 1)? as usize; + let length = args.get_u32(activation, 2)? as usize; if !Object::ptr_eq(this, bytearray) { if let Some(bytearray_read) = this.as_bytearray() { @@ -127,7 +111,7 @@ pub fn read_bytes<'gc>( let mut ba_write = bytearray .as_bytearray_mut() - .ok_or("ArgumentError: Parameter must be a bytearray")?; + .expect("Parameter must be a bytearray"); ba_write .write_at(to_write, offset) diff --git a/core/src/avm2/globals/flash/utils/timer.rs b/core/src/avm2/globals/flash/utils/timer.rs index 202e57c49695..bada3fc4772d 100644 --- a/core/src/avm2/globals/flash/utils/timer.rs +++ b/core/src/avm2/globals/flash/utils/timer.rs @@ -65,7 +65,9 @@ pub fn start<'gc>( &Multiname::new(namespaces.flash_utils_internal, "onUpdate"), activation, )? - .coerce_to_object(activation)?; + .as_object() + .expect("Internal function is object"); + // Note - we deliberately do *not* check if currentCount is less than repeatCount. // Calling 'start' on a timer that has currentCount >= repeatCount will tick exactly // once, and then stop immediately. This is handled by Timer.onUpdate From 64cd9e21c66e4c2aa21de91024e63c3b157777f3 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 6 Oct 2024 17:24:14 -0700 Subject: [PATCH 04/10] avm2: Remove more uses of `coerce_to_object`, especially in `Graphics` --- .../avm2/globals/flash/display/bitmap_data.rs | 2 +- .../avm2/globals/flash/display/graphics.rs | 112 ++++++++++-------- .../flash/display3D/vertex_buffer_3d.rs | 19 +-- core/src/avm2/globals/string.rs | 8 +- 4 files changed, 76 insertions(+), 65 deletions(-) diff --git a/core/src/avm2/globals/flash/display/bitmap_data.rs b/core/src/avm2/globals/flash/display/bitmap_data.rs index 72e63f8f3eb6..3f80851b339b 100644 --- a/core/src/avm2/globals/flash/display/bitmap_data.rs +++ b/core/src/avm2/globals/flash/display/bitmap_data.rs @@ -469,7 +469,7 @@ pub fn set_pixels<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { let rectangle = args.get_object(activation, 0, "rect")?; - let bytearray = args.get_object(activation, 0, "inputByteArray")?; + let bytearray = args.get_object(activation, 1, "inputByteArray")?; if let Some(bitmap_data) = this.as_bitmap_data() { let (x, y, width, height) = get_rectangle_x_y_width_height(activation, rectangle)?; diff --git a/core/src/avm2/globals/flash/display/graphics.rs b/core/src/avm2/globals/flash/display/graphics.rs index d2added76178..bc782253a9c0 100644 --- a/core/src/avm2/globals/flash/display/graphics.rs +++ b/core/src/avm2/globals/flash/display/graphics.rs @@ -4,7 +4,7 @@ #![allow(clippy::doc_lazy_continuation)] use crate::avm2::activation::Activation; -use crate::avm2::error::{make_error_2004, make_error_2008, Error2004Type}; +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::object::{Object, TObject, VectorObject}; use crate::avm2::parameters::ParametersExt; @@ -103,14 +103,18 @@ pub fn begin_gradient_fill<'gc>( let gradient_type = args.get_string(activation, 0)?; let gradient_type = parse_gradient_type(activation, gradient_type)?; let colors = args.get_object(activation, 1, "colors")?; - let alphas = args.get_object(activation, 2, "alphas")?; - let ratios = args.get_object(activation, 3, "ratios")?; + let alphas = args.try_get_object(activation, 2); + let ratios = args + .try_get_object(activation, 3) + .expect("Ratios should never be null"); + let records = build_gradient_records( activation, &colors.as_array_storage().expect("Guaranteed by AS"), - &alphas.as_array_storage().expect("Guaranteed by AS"), + alphas, &ratios.as_array_storage().expect("Guaranteed by AS"), )?; + let matrix = if let Some(matrix) = args.try_get_object(activation, 4) { Matrix::from(object_to_matrix(matrix, activation)?) } else { @@ -159,20 +163,32 @@ pub fn begin_gradient_fill<'gc>( fn build_gradient_records<'gc>( activation: &mut Activation<'_, 'gc>, colors: &ArrayStorage<'gc>, - alphas: &ArrayStorage<'gc>, + alphas: Option>, ratios: &ArrayStorage<'gc>, ) -> Result, Error<'gc>> { - let length = colors.length().min(alphas.length()).min(ratios.length()); + let alphas = alphas.as_ref().map(|o| o.as_array_storage().unwrap()); + + let mut length = colors.length().min(ratios.length()); + if let Some(ref alphas) = alphas { + length = length.min(alphas.length()); + } + let mut records = Vec::with_capacity(length); for i in 0..length { let color = colors .get(i) .expect("Length should be guaranteed") .coerce_to_u32(activation)?; - let alpha = alphas - .get(i) - .expect("Length should be guaranteed") - .coerce_to_number(activation)? as f32; + + let alpha = if let Some(ref alphas) = alphas { + alphas + .get(i) + .expect("Length should be guaranteed") + .coerce_to_number(activation)? as f32 + } else { + 1.0 + }; + let ratio = ratios .get(i) .expect("Length should be guaranteed") @@ -833,12 +849,15 @@ pub fn line_gradient_style<'gc>( let gradient_type = args.get_string(activation, 0); let gradient_type = parse_gradient_type(activation, gradient_type?)?; let colors = args.get_object(activation, 1, "colors")?; - let alphas = args.get_object(activation, 2, "alphas")?; - let ratios = args.get_object(activation, 3, "ratios")?; + let alphas = args.try_get_object(activation, 2); + let ratios = args + .try_get_object(activation, 3) + .expect("Ratios should never be null"); + let records = build_gradient_records( activation, &colors.as_array_storage().expect("Guaranteed by AS"), - &alphas.as_array_storage().expect("Guaranteed by AS"), + alphas, &ratios.as_array_storage().expect("Guaranteed by AS"), )?; let matrix = if let Some(matrix) = args.try_get_object(activation, 4) { @@ -1416,27 +1435,23 @@ 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)? - .coerce_to_object(activation)?; + let commands = obj.get_public_property("commands", activation)?.as_object(); - let data = obj - .get_public_property("data", activation)? - .coerce_to_object(activation)?; + let data = obj.get_public_property("data", activation)?.as_object(); let winding = obj .get_public_property("winding", activation)? .coerce_to_string(activation)?; - process_commands( - activation, - drawing, - &commands - .as_vector_storage() - .expect("commands is not a Vector"), - &data.as_vector_storage().expect("data is not a Vector"), - winding, - )?; + if let (Some(commands), Some(data)) = (commands, data) { + process_commands( + activation, + drawing, + &commands.as_vector_storage().unwrap(), + &data.as_vector_storage().unwrap(), + winding, + )?; + } } else if class == activation.avm2().class_defs().graphicssolidfill { let style = handle_solid_fill(activation, obj)?; drawing.set_fill_style(Some(style)); @@ -1458,11 +1473,13 @@ fn handle_igraphics_data<'gc>( caps_to_cap_style(caps.ok()) }; let fill = { - let fill = obj - .get_public_property("fill", activation)? - .coerce_to_object(activation)?; + let fill = obj.get_public_property("fill", activation)?.as_object(); - handle_igraphics_fill(activation, drawing, &fill)? + if let Some(fill) = fill { + handle_igraphics_fill(activation, drawing, &fill)? + } else { + None + } }; let joints = obj @@ -1588,17 +1605,17 @@ fn handle_gradient_fill<'gc>( activation: &mut Activation<'_, 'gc>, obj: &Object<'gc>, ) -> Result> { - let alphas = obj - .get_public_property("alphas", activation)? - .coerce_to_object(activation)?; + let alphas = obj.get_public_property("alphas", activation)?.as_object(); let colors = obj .get_public_property("colors", activation)? - .coerce_to_object(activation)?; + .as_object() + .ok_or_else(|| make_error_2007(activation, "colors"))?; let ratios = obj .get_public_property("ratios", activation)? - .coerce_to_object(activation)?; + .as_object() + .expect("Ratios should never be null"); let gradient_type = { let gradient_type = obj @@ -1610,15 +1627,12 @@ fn handle_gradient_fill<'gc>( let records = build_gradient_records( activation, &colors.as_array_storage().expect("Guaranteed by AS"), - &alphas.as_array_storage().expect("Guaranteed by AS"), + alphas, &ratios.as_array_storage().expect("Guaranteed by AS"), )?; let matrix = { - let matrix = obj - .get_public_property("matrix", activation) - .ok() - .and_then(|mat| mat.coerce_to_object(activation).ok()); + let matrix = obj.get_public_property("matrix", activation)?.as_object(); match matrix { Some(matrix) => Matrix::from(object_to_matrix(matrix, activation)?), @@ -1680,18 +1694,18 @@ fn handle_bitmap_fill<'gc>( ) -> Result> { let bitmap_data = obj .get_public_property("bitmapData", activation)? - .coerce_to_object(activation)? + .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) - .and_then(|prop| { - let matrix = prop.coerce_to_object(activation)?; - - let matrix = Matrix::from(object_to_matrix(matrix, activation)?); + .get_public_property("matrix", activation)? + .as_object() + .and_then(|matrix| { + let matrix = Matrix::from(object_to_matrix(matrix, activation).ok()?); - Ok(matrix) + Some(matrix) }) .unwrap_or(Matrix::IDENTITY); diff --git a/core/src/avm2/globals/flash/display3D/vertex_buffer_3d.rs b/core/src/avm2/globals/flash/display3D/vertex_buffer_3d.rs index 838ef9663771..a01b741e8d68 100644 --- a/core/src/avm2/globals/flash/display3D/vertex_buffer_3d.rs +++ b/core/src/avm2/globals/flash/display3D/vertex_buffer_3d.rs @@ -1,7 +1,6 @@ +use crate::avm2::activation::Activation; +use crate::avm2::object::{ClassObject, TObject}; use crate::avm2::parameters::ParametersExt; -use crate::avm2::Activation; -use crate::avm2::ClassObject; -use crate::avm2::TObject; use crate::avm2::Value; use crate::avm2::{Error, Object}; @@ -18,14 +17,11 @@ pub fn upload_from_byte_array<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(vertex_buffer) = this.as_vertex_buffer() { - let byte_array = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; + let byte_array = args.get_object(activation, 0, "data")?; let byte_array = byte_array .as_bytearray() - .ok_or_else(|| Error::from("ArgumentError: Parameter must be a ByteArray"))?; + .expect("Parameter must be a ByteArray"); let byte_offset = args.get_u32(activation, 1)?; let start_vertex = args.get_u32(activation, 2)?; @@ -55,14 +51,11 @@ pub fn upload_from_vector<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(vertex_buffer) = this.as_vertex_buffer() { - let vector = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; + let vector = args.get_object(activation, 0, "data")?; let vector = vector .as_vector_storage() - .ok_or_else(|| Error::from("ArgumentError: Parameter must be a Vector"))?; + .expect("Parameter must be a Vector"); let start_vertex = args.get_u32(activation, 1)?; let num_vertices = args.get_u32(activation, 2)?; diff --git a/core/src/avm2/globals/string.rs b/core/src/avm2/globals/string.rs index ffb729e213ae..2e4ebbbda6da 100644 --- a/core/src/avm2/globals/string.rs +++ b/core/src/avm2/globals/string.rs @@ -290,7 +290,9 @@ fn match_s<'gc>( let string = pattern.coerce_to_string(activation)?; regexp_class.construct(activation, &[Value::String(string)])? } else { - pattern.coerce_to_object(activation)? + pattern + .as_object() + .expect("Regexp objects must be Value::Object") }; if let Some(mut regexp) = pattern.as_regexp_mut(activation.context.gc_context) { @@ -400,7 +402,9 @@ fn search<'gc>( let string = pattern.coerce_to_string(activation)?; regexp_class.construct(activation, &[Value::String(string)])? } else { - pattern.coerce_to_object(activation)? + pattern + .as_object() + .expect("Regexp objects must be Value::Object") }; if let Some(mut regexp) = pattern.as_regexp_mut(activation.context.gc_context) { From e82239c9259b5c557a799b5847be2665ca604bc8 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 6 Oct 2024 17:41:29 -0700 Subject: [PATCH 05/10] avm2: Remove a few more uses of `coerce_to_object` --- .../flash/display3D/index_buffer_3d.rs | 9 +++----- core/src/avm2/globals/flash/media/sound.rs | 22 ++++++++----------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/core/src/avm2/globals/flash/display3D/index_buffer_3d.rs b/core/src/avm2/globals/flash/display3D/index_buffer_3d.rs index 551b9c2ee19b..6bbe50a26757 100644 --- a/core/src/avm2/globals/flash/display3D/index_buffer_3d.rs +++ b/core/src/avm2/globals/flash/display3D/index_buffer_3d.rs @@ -20,7 +20,7 @@ pub fn upload_from_byte_array<'gc>( let byte_array = args.get_object(activation, 0, "byteArray")?; let byte_array = byte_array .as_bytearray() - .ok_or_else(|| Error::from("ArgumentError: Parameter must be a ByteArray"))?; + .expect("Parameter must be a ByteArray"); let byte_offset = args.get_u32(activation, 1)?; let start_offset = args.get_u32(activation, 2)?; @@ -47,14 +47,11 @@ pub fn upload_from_vector<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(index_buffer) = this.as_index_buffer() { - let vector = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; + let vector = args.get_object(activation, 0, "data")?; let vector = vector .as_vector_storage() - .ok_or_else(|| Error::from("ArgumentError: Parameter must be a Vector"))?; + .expect("Parameter must be a Vector"); let start_offset = args.get_u32(activation, 1)?; let count = args.get_u32(activation, 2)?; diff --git a/core/src/avm2/globals/flash/media/sound.rs b/core/src/avm2/globals/flash/media/sound.rs index 2e1725f1e014..f6ce1c034888 100644 --- a/core/src/avm2/globals/flash/media/sound.rs +++ b/core/src/avm2/globals/flash/media/sound.rs @@ -199,19 +199,15 @@ pub fn extract<'gc>( ) -> Result, Error<'gc>> { avm2_stub_method!(activation, "flash.media.Sound", "extract"); - let bytearray = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_object(activation)?; - let length = args - .get(1) - .unwrap_or(&Value::Number(0.0)) - .coerce_to_number(activation)?; - - if let Some(mut bytearray) = bytearray.as_bytearray_mut() { - bytearray - .write_bytes(vec![0u8; length.ceil() as usize].as_slice()) - .map_err(|e| e.to_avm(activation))?; + let bytearray = args.try_get_object(activation, 0); + let length = args.get_f64(activation, 1)?; + + if let Some(bytearray) = bytearray { + if let Some(mut bytearray) = bytearray.as_bytearray_mut() { + bytearray + .write_bytes(vec![0u8; length.ceil() as usize].as_slice()) + .map_err(|e| e.to_avm(activation))?; + } } Ok(Value::Undefined) From e4d16eab38e6d7b12c285f25e411a567a975f1b9 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 6 Oct 2024 18:22:00 -0700 Subject: [PATCH 06/10] avm2: Make `SharedObject` an `Object` variant --- core/src/avm2/globals.rs | 3 + .../avm2/globals/flash/net/SharedObject.as | 8 +- .../avm2/globals/flash/net/shared_object.rs | 97 ++++++-------- core/src/avm2/object.rs | 12 ++ core/src/avm2/object/shared_object_object.rs | 120 ++++++++++++++++++ 5 files changed, 177 insertions(+), 63 deletions(-) create mode 100644 core/src/avm2/object/shared_object_object.rs diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 916a17fcfbfa..123c376b77b9 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -170,6 +170,7 @@ pub struct SystemClasses<'gc> { pub dictionary: ClassObject<'gc>, pub id3info: ClassObject<'gc>, pub textrun: ClassObject<'gc>, + pub sharedobject: ClassObject<'gc>, } #[derive(Clone, Collect)] @@ -327,6 +328,7 @@ impl<'gc> SystemClasses<'gc> { dictionary: object, id3info: object, textrun: object, + sharedobject: object, } } } @@ -929,6 +931,7 @@ pub fn init_native_system_classes(activation: &mut Activation<'_, '_>) { ("flash.net", "URLVariables", urlvariables), ("flash.net", "FileReference", filereference), ("flash.net", "FileFilter", filefilter), + ("flash.net", "SharedObject", sharedobject), ("flash.utils", "ByteArray", bytearray), ("flash.utils", "Dictionary", dictionary), ("flash.system", "ApplicationDomain", application_domain), diff --git a/core/src/avm2/globals/flash/net/SharedObject.as b/core/src/avm2/globals/flash/net/SharedObject.as index 81207be1ad1a..0f8450c9b945 100644 --- a/core/src/avm2/globals/flash/net/SharedObject.as +++ b/core/src/avm2/globals/flash/net/SharedObject.as @@ -4,9 +4,10 @@ package flash.net { namespace ruffle = "__ruffle__"; + [Ruffle(InstanceAllocator)] public class SharedObject extends EventDispatcher { public function SharedObject() { - this.data = {}; + // Unreachable; the allocator always throws } // NOTE: We currently always use AMF3 serialization. @@ -30,9 +31,6 @@ package flash.net { // but we don't support them yet } - // note: this is supposed to be a read-only property - public var data: Object; - - ruffle var _ruffleName: String; + public native function get data():Object; } } diff --git a/core/src/avm2/globals/flash/net/shared_object.rs b/core/src/avm2/globals/flash/net/shared_object.rs index 53212e12f1ad..569a36d2c14c 100644 --- a/core/src/avm2/globals/flash/net/shared_object.rs +++ b/core/src/avm2/globals/flash/net/shared_object.rs @@ -2,10 +2,8 @@ use crate::avm2::error::error; use crate::avm2::object::TObject; -use crate::avm2::Error::AvmError; -use crate::avm2::Multiname; +pub use crate::avm2::object::{shared_object_allocator, SharedObjectObject}; use crate::avm2::{Activation, Error, Object, Value}; -use crate::string::AvmString; use crate::{avm2_stub_getter, avm2_stub_method, avm2_stub_setter}; use flash_lso::types::{AMFVersion, Lso}; use std::borrow::Cow; @@ -36,7 +34,7 @@ fn new_lso<'gc>( pub fn get_local<'gc>( activation: &mut Activation<'_, 'gc>, - this: Object<'gc>, + _this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { // TODO: It appears that Flash does some kind of escaping here: @@ -148,19 +146,7 @@ pub fn get_local<'gc>( return Ok((*so).into()); } - // Data property only should exist when created with getLocal/Remote - let sharedobject_cls = this; // `this` of a static method is the class - let this = sharedobject_cls.construct(activation, &[])?; - - // Set the internal name - let ruffle_name = Multiname::new(activation.avm2().namespaces.__ruffle__, "_ruffleName"); - this.set_property( - &ruffle_name, - AvmString::new_utf8(activation.context.gc_context, &full_name).into(), - activation, - )?; - - let mut data = Value::Undefined; + let mut data = None; // Load the data object from storage if it existed prior if let Some(saved) = activation.context.storage.get(&full_name) { @@ -169,23 +155,36 @@ pub fn get_local<'gc>( } } - if data == Value::Undefined { + let data = if let Some(data) = data { + data + } else { // No data; create a fresh data object. - data = activation + activation .avm2() .classes() .object .construct(activation, &[])? - .into(); - } + }; + + let created_shared_object = + SharedObjectObject::from_data_and_name(activation, data, full_name.clone()); - this.set_public_property("data", data, activation)?; activation .context .avm2_shared_objects - .insert(full_name, this); + .insert(full_name, created_shared_object.into()); - Ok(this.into()) + Ok(created_shared_object.into()) +} + +pub fn get_data<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let shared_object = this.as_shared_object().unwrap(); + + Ok(shared_object.data().into()) } pub fn flush<'gc>( @@ -193,26 +192,21 @@ pub fn flush<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let data = this - .get_public_property("data", activation)? - .coerce_to_object(activation)?; + let shared_object = this.as_shared_object().unwrap(); - let ruffle_name = Multiname::new(activation.avm2().namespaces.__ruffle__, "_ruffleName"); - let name = this - .get_property(&ruffle_name, activation)? - .coerce_to_string(activation)?; - let name = name.to_utf8_lossy(); + let data = shared_object.data(); + let name = shared_object.name(); - let mut lso = new_lso(activation, &name, data)?; + let mut lso = new_lso(activation, name, data)?; // Flash does not write empty LSOs to disk if lso.body.is_empty() { Ok("flushed".into()) } else { let bytes = flash_lso::write::write_to_bytes(&mut lso).unwrap_or_default(); - if activation.context.storage.put(&name, &bytes) { + if activation.context.storage.put(name, &bytes) { Ok("flushed".into()) } else { - Err(AvmError(error( + Err(Error::AvmError(error( activation, "Error #2130: Unable to flush SharedObject.", 2130, @@ -227,17 +221,12 @@ pub fn get_size<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let data = this - .get_public_property("data", activation)? - .coerce_to_object(activation)?; + let shared_object = this.as_shared_object().unwrap(); - let ruffle_name = Multiname::new(activation.avm2().namespaces.__ruffle__, "_ruffleName"); - let name = this - .get_property(&ruffle_name, activation)? - .coerce_to_string(activation)?; - let name = name.to_utf8_lossy(); + let data = shared_object.data(); + let name = shared_object.name(); - let mut lso = new_lso(activation, &name, data)?; + let mut lso = new_lso(activation, name, data)?; // Flash returns 0 for empty LSOs, but the actual number of bytes (including the header) otherwise if lso.body.is_empty() { Ok(0.into()) @@ -261,22 +250,14 @@ pub fn clear<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - // Create a fresh data object. - let data = activation - .avm2() - .classes() - .object - .construct(activation, &[])? - .into(); - this.set_public_property("data", data, activation)?; + let shared_object = this.as_shared_object().unwrap(); + + // Clear the local data object. + shared_object.reset_data(activation)?; // Delete data from storage backend. - let ruffle_name = Multiname::new(activation.avm2().namespaces.__ruffle__, "_ruffleName"); - let name = this - .get_property(&ruffle_name, activation)? - .coerce_to_string(activation)?; - let name = name.to_utf8_lossy(); - activation.context.storage.remove_key(&name); + let name = shared_object.name(); + activation.context.storage.remove_key(name); Ok(Value::Undefined) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 66f67b113d9d..a81c82a7ba3a 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -55,6 +55,7 @@ mod regexp_object; mod responder_object; mod script_object; mod shader_data_object; +mod shared_object_object; mod socket_object; mod sound_object; mod soundchannel_object; @@ -127,6 +128,9 @@ pub use crate::avm2::object::script_object::{ pub use crate::avm2::object::shader_data_object::{ shader_data_allocator, ShaderDataObject, ShaderDataObjectWeak, }; +pub use crate::avm2::object::shared_object_object::{ + shared_object_allocator, SharedObjectObject, SharedObjectObjectWeak, +}; pub use crate::avm2::object::socket_object::{socket_allocator, SocketObject, SocketObjectWeak}; pub use crate::avm2::object::sound_object::{ sound_allocator, QueuedPlay, SoundObject, SoundObjectWeak, @@ -198,6 +202,7 @@ use crate::font::Font; FileReferenceObject(FileReferenceObject<'gc>), FontObject(FontObject<'gc>), LocalConnectionObject(LocalConnectionObject<'gc>), + SharedObjectObject(SharedObjectObject<'gc>), } )] pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy { @@ -1368,6 +1373,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy fn as_file_reference(&self) -> Option> { None } + + fn as_shared_object(&self) -> Option> { + None + } } pub enum ObjectPtr {} @@ -1419,6 +1428,7 @@ impl<'gc> Object<'gc> { Self::FileReferenceObject(o) => WeakObject::FileReferenceObject(FileReferenceObjectWeak(Gc::downgrade(o.0))), 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))), } } } @@ -1480,6 +1490,7 @@ pub enum WeakObject<'gc> { FileReferenceObject(FileReferenceObjectWeak<'gc>), FontObject(FontObjectWeak<'gc>), LocalConnectionObject(LocalConnectionObjectWeak<'gc>), + SharedObjectObject(SharedObjectObjectWeak<'gc>), } impl<'gc> WeakObject<'gc> { @@ -1524,6 +1535,7 @@ impl<'gc> WeakObject<'gc> { Self::FileReferenceObject(o) => FileReferenceObject(o.0.upgrade(mc)?).into(), 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(), }) } } diff --git a/core/src/avm2/object/shared_object_object.rs b/core/src/avm2/object/shared_object_object.rs new file mode 100644 index 000000000000..5cdc603ba901 --- /dev/null +++ b/core/src/avm2/object/shared_object_object.rs @@ -0,0 +1,120 @@ +//! Object representation for SharedObjects + +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::Error; +use gc_arena::barrier::unlock; +use gc_arena::{lock::Lock, Collect, Gc, GcWeak}; +use std::fmt::Debug; + +/// SharedObjects cannot be constructed by AS. +pub fn shared_object_allocator<'gc>( + class: ClassObject<'gc>, + activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + let class_name = class.inner_class_definition().name().local_name(); + + Err(Error::AvmError(argument_error( + activation, + &format!("Error #2012: {class_name}$ class cannot be instantiated."), + 2012, + )?)) +} + +#[derive(Clone, Collect, Copy)] +#[collect(no_drop)] +pub struct SharedObjectObject<'gc>(pub Gc<'gc, SharedObjectObjectData<'gc>>); + +#[derive(Clone, Collect, Copy, Debug)] +#[collect(no_drop)] +pub struct SharedObjectObjectWeak<'gc>(pub GcWeak<'gc, SharedObjectObjectData<'gc>>); + +#[derive(Clone, Collect)] +#[collect(no_drop)] +#[repr(C, align(8))] +pub struct SharedObjectObjectData<'gc> { + /// Base script object + base: ScriptObjectData<'gc>, + + /// The SharedObject data that this SharedObjectObject holds. + data: Lock>, + + /// The name of this SharedObject. + name: String, +} + +const _: () = assert!(std::mem::offset_of!(SharedObjectObjectData, base) == 0); +const _: () = assert!( + std::mem::align_of::() == std::mem::align_of::() +); + +impl<'gc> SharedObjectObject<'gc> { + pub fn from_data_and_name( + activation: &mut Activation<'_, 'gc>, + data: Object<'gc>, + name: String, + ) -> Self { + let class = activation.avm2().classes().sharedobject; + let base = ScriptObjectData::new(class); + + SharedObjectObject(Gc::new( + activation.context.gc_context, + SharedObjectObjectData { + base, + data: Lock::new(data), + name, + }, + )) + } + + pub fn data(&self) -> Object<'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, &[])?; + + unlock!( + Gc::write(activation.gc(), self.0), + SharedObjectObjectData, + data + ) + .set(empty_data); + + Ok(()) + } + + pub fn name(&self) -> &String { + &self.0.name + } +} + +impl<'gc> TObject<'gc> for SharedObjectObject<'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_shared_object(&self) -> Option> { + Some(*self) + } +} + +impl std::fmt::Debug for SharedObjectObject<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "SharedObject") + } +} From 1acb7ae2b47ab3adebcc42afef4498d343a1b8e4 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 6 Oct 2024 20:40:08 -0700 Subject: [PATCH 07/10] avm2: Ensure Matrix3D's `rawData` is never null --- .../globals/flash/display3D/context_3d.rs | 7 +- core/src/avm2/globals/flash/geom/Matrix3D.as | 1479 +++++++++-------- 2 files changed, 745 insertions(+), 741 deletions(-) diff --git a/core/src/avm2/globals/flash/display3D/context_3d.rs b/core/src/avm2/globals/flash/display3D/context_3d.rs index 77fb83ff85bd..19ba9cc40d6e 100644 --- a/core/src/avm2/globals/flash/display3D/context_3d.rs +++ b/core/src/avm2/globals/flash/display3D/context_3d.rs @@ -298,14 +298,17 @@ pub fn set_program_constants_from_matrix<'gc>( if user_transposed_matrix { matrix = matrix .call_public_property("clone", &[], activation)? - .coerce_to_object(activation)?; + .as_object() + .expect("Matrix3D.clone returns Object"); matrix.call_public_property("transpose", &[], activation)?; } let matrix_raw_data = matrix .get_public_property("rawData", activation)? - .coerce_to_object(activation)?; + .as_object() + .expect("rawData cannot be null"); + let matrix_raw_data = matrix_raw_data .as_vector_storage() .unwrap() diff --git a/core/src/avm2/globals/flash/geom/Matrix3D.as b/core/src/avm2/globals/flash/geom/Matrix3D.as index e6126af20bdc..22e7c5eb8fdf 100644 --- a/core/src/avm2/globals/flash/geom/Matrix3D.as +++ b/core/src/avm2/globals/flash/geom/Matrix3D.as @@ -1,749 +1,750 @@ // Based on the MIT-licensed OpenFL code https://github.com/openfl/openfl/blob/develop/src/openfl/geom/Matrix3D.hx package flash.geom { - import __ruffle__.stub_method; - - public class Matrix3D { - - // The 4x4 matrix data, stored in column-major order - private var _rawData:Vector.; - - public function get rawData():Vector. { - return this._rawData.concat(); - } - - public function set rawData(value:Vector.):void { - this._rawData = value.concat(); - } - - public function Matrix3D(v:Vector. = null) { - if (v != null && v.length == 16) { - this._rawData = v.concat(); - } - else { - this.identity(); - } - } - - 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 - ]; - } - - public function appendTranslation(x:Number, y:Number, z:Number):void { - this._rawData[12] += x; - this._rawData[13] += y; - this._rawData[14] += z; - } - - public function appendRotation(degrees:Number, axis:Vector3D, pivotPoint:Vector3D = null):void { - var tx:Number, ty:Number, tz:Number; - tx = ty = tz = 0; - - if (pivotPoint != null) { - tx = pivotPoint.x; - ty = pivotPoint.y; - tz = pivotPoint.z; - } - var radian = degrees * Math.PI / 180; - var cos = Math.cos(radian); - var sin = Math.sin(radian); - var x = axis.x; - var y = axis.y; - var z = axis.z; - var x2 = x * x; - var y2 = y * y; - var z2 = z * z; - var ls = x2 + y2 + z2; - if (ls != 0) { - var l = Math.sqrt(ls); - x /= l; - y /= l; - z /= l; - x2 /= ls; - y2 /= ls; - z2 /= ls; - } - var ccos = 1 - cos; - var m = new Matrix3D(); - - var d = m.rawData; - d[0] = x2 + (y2 + z2) * cos; - d[1] = x * y * ccos + z * sin; - d[2] = x * z * ccos - y * sin; - d[4] = x * y * ccos - z * sin; - d[5] = y2 + (x2 + z2) * cos; - d[6] = y * z * ccos + x * sin; - d[8] = x * z * ccos + y * sin; - d[9] = y * z * ccos - x * sin; - d[10] = z2 + (x2 + y2) * cos; - d[12] = (tx * (y2 + z2) - x * (ty * y + tz * z)) * ccos + (ty * z - tz * y) * sin; - d[13] = (ty * (x2 + z2) - y * (tx * x + tz * z)) * ccos + (tz * x - tx * z) * sin; - d[14] = (tz * (x2 + y2) - z * (tx * x + ty * y)) * ccos + (tx * y - ty * x) * sin; - m.rawData = d; - - this.append(m); - } - - [API("674")] - public function copyRawDataFrom(vector:Vector., index:uint = 0, transpose:Boolean = false):void { - if (transpose) { - this.transpose(); - } - - var length = vector.length - index; - - for (var i = 0; i < length; i++) { - this._rawData[i] = vector[i + index]; - } - - if (transpose) { - this.transpose(); - } - } - - // Based on https://github.com/openfl/openfl/blob/develop/src/openfl/geom/Matrix3D.hx#L542C1-L573 - [API("674")] - public function copyRowTo(row:uint, vector3D:Vector3D):void { - if (row > 3) { - throw new ArgumentError("Error #2004: One of the parameters is invalid.", 2004); - } - - switch (row) { - case 0: - vector3D.x = rawData[0]; - vector3D.y = rawData[4]; - vector3D.z = rawData[8]; - vector3D.w = rawData[12]; - break; - case 1: - vector3D.x = rawData[1]; - vector3D.y = rawData[5]; - vector3D.z = rawData[9]; - vector3D.w = rawData[13]; - break; - case 2: - vector3D.x = rawData[2]; - vector3D.y = rawData[6]; - vector3D.z = rawData[10]; - vector3D.w = rawData[14]; - break; - case 3: - vector3D.x = rawData[3]; - vector3D.y = rawData[7]; - vector3D.z = rawData[11]; - vector3D.w = rawData[15]; - break; - } - } - - // Based on https://github.com/openfl/openfl/blob/develop/src/openfl/geom/Matrix3D.hx#L504-L534 - [API("674")] - public function copyRowFrom(row:uint, vector3D:Vector3D):void { - if (row > 3) { - throw new ArgumentError("Error #2004: One of the parameters is invalid.", 2004); - } - - switch (row) { - case 0: - _rawData[0] = vector3D.x; - _rawData[4] = vector3D.y; - _rawData[8] = vector3D.z; - _rawData[12] = vector3D.w; - break; - case 1: - _rawData[1] = vector3D.x; - _rawData[5] = vector3D.y; - _rawData[9] = vector3D.z; - _rawData[13] = vector3D.w; - break; - case 2: - _rawData[2] = vector3D.x; - _rawData[6] = vector3D.y; - _rawData[10] = vector3D.z; - _rawData[14] = vector3D.w; - break; - case 3: - _rawData[3] = vector3D.x; - _rawData[7] = vector3D.y; - _rawData[11] = vector3D.z; - _rawData[15] = vector3D.w; - break; - } - } - - public function deltaTransformVector(v:Vector3D):Vector3D { - var x:Number = this._rawData[0] * v.x + this._rawData[4] * v.y + this._rawData[8] * v.z; - var y:Number = this._rawData[1] * v.x + this._rawData[5] * v.y + this._rawData[9] * v.z; - var z:Number = this._rawData[2] * v.x + this._rawData[6] * v.y + this._rawData[10] * v.z; - var w:Number = this._rawData[3] * v.x + this._rawData[7] * v.y + this._rawData[11] * v.z; - return new Vector3D(x, y, z, w); - } - - public function transformVector(v:Vector3D):Vector3D { - var x:Number = this._rawData[0] * v.x + this._rawData[4] * v.y + this._rawData[8] * v.z + this._rawData[12]; - var y:Number = this._rawData[1] * v.x + this._rawData[5] * v.y + this._rawData[9] * v.z + this._rawData[13]; - var z:Number = this._rawData[2] * v.x + this._rawData[6] * v.y + this._rawData[10] * v.z + this._rawData[14]; - var w:Number = this._rawData[3] * v.x + this._rawData[7] * v.y + this._rawData[11] * v.z + this._rawData[15]; - return new Vector3D(x, y, z, w); - } - - public function transformVectors(vin:Vector., vout:Vector.):void { - if (vin == null) { - throw new TypeError("Error #2007: Parameter vin must be non-null.", 2007); + import __ruffle__.stub_method; + + public class Matrix3D { + + // The 4x4 matrix data, stored in column-major order + private var _rawData:Vector.; + + public function get rawData():Vector. { + return this._rawData.AS3::concat(); + } + + public function set rawData(value:Vector.):void { + if (value != null) { + this._rawData = value.AS3::concat(); + } + } + + public function Matrix3D(v:Vector. = null) { + if (v != null && v.length == 16) { + this._rawData = v.AS3::concat(); + } else { + this.identity(); + } + } + + 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 + ]; + } + + public function appendTranslation(x:Number, y:Number, z:Number):void { + this._rawData[12] += x; + this._rawData[13] += y; + this._rawData[14] += z; + } + + public function appendRotation(degrees:Number, axis:Vector3D, pivotPoint:Vector3D = null):void { + var tx:Number, ty:Number, tz:Number; + tx = ty = tz = 0; + + if (pivotPoint != null) { + tx = pivotPoint.x; + ty = pivotPoint.y; + tz = pivotPoint.z; + } + var radian = degrees * Math.PI / 180; + var cos = Math.cos(radian); + var sin = Math.sin(radian); + var x = axis.x; + var y = axis.y; + var z = axis.z; + var x2 = x * x; + var y2 = y * y; + var z2 = z * z; + var ls = x2 + y2 + z2; + if (ls != 0) { + var l = Math.sqrt(ls); + x /= l; + y /= l; + z /= l; + x2 /= ls; + y2 /= ls; + z2 /= ls; + } + var ccos = 1 - cos; + var m = new Matrix3D(); + + var d = m.rawData; + d[0] = x2 + (y2 + z2) * cos; + d[1] = x * y * ccos + z * sin; + d[2] = x * z * ccos - y * sin; + d[4] = x * y * ccos - z * sin; + d[5] = y2 + (x2 + z2) * cos; + d[6] = y * z * ccos + x * sin; + d[8] = x * z * ccos + y * sin; + d[9] = y * z * ccos - x * sin; + d[10] = z2 + (x2 + y2) * cos; + d[12] = (tx * (y2 + z2) - x * (ty * y + tz * z)) * ccos + (ty * z - tz * y) * sin; + d[13] = (ty * (x2 + z2) - y * (tx * x + tz * z)) * ccos + (tz * x - tx * z) * sin; + d[14] = (tz * (x2 + y2) - z * (tx * x + ty * y)) * ccos + (tx * y - ty * x) * sin; + m.rawData = d; + + this.append(m); + } + + [API("674")] + public function copyRawDataFrom(vector:Vector., index:uint = 0, transpose:Boolean = false):void { + if (transpose) { + this.transpose(); + } + + var length = vector.length - index; + + for (var i = 0; i < length; i++) { + this._rawData[i] = vector[i + index]; + } + + if (transpose) { + this.transpose(); + } + } + + // Based on https://github.com/openfl/openfl/blob/develop/src/openfl/geom/Matrix3D.hx#L542C1-L573 + [API("674")] + public function copyRowTo(row:uint, vector3D:Vector3D):void { + if (row > 3) { + throw new ArgumentError("Error #2004: One of the parameters is invalid.", 2004); + } + + switch (row) { + case 0: + vector3D.x = rawData[0]; + vector3D.y = rawData[4]; + vector3D.z = rawData[8]; + vector3D.w = rawData[12]; + break; + case 1: + vector3D.x = rawData[1]; + vector3D.y = rawData[5]; + vector3D.z = rawData[9]; + vector3D.w = rawData[13]; + break; + case 2: + vector3D.x = rawData[2]; + vector3D.y = rawData[6]; + vector3D.z = rawData[10]; + vector3D.w = rawData[14]; + break; + case 3: + vector3D.x = rawData[3]; + vector3D.y = rawData[7]; + vector3D.z = rawData[11]; + vector3D.w = rawData[15]; + break; + } + } + + // Based on https://github.com/openfl/openfl/blob/develop/src/openfl/geom/Matrix3D.hx#L504-L534 + [API("674")] + public function copyRowFrom(row:uint, vector3D:Vector3D):void { + if (row > 3) { + throw new ArgumentError("Error #2004: One of the parameters is invalid.", 2004); + } + + switch (row) { + case 0: + _rawData[0] = vector3D.x; + _rawData[4] = vector3D.y; + _rawData[8] = vector3D.z; + _rawData[12] = vector3D.w; + break; + case 1: + _rawData[1] = vector3D.x; + _rawData[5] = vector3D.y; + _rawData[9] = vector3D.z; + _rawData[13] = vector3D.w; + break; + case 2: + _rawData[2] = vector3D.x; + _rawData[6] = vector3D.y; + _rawData[10] = vector3D.z; + _rawData[14] = vector3D.w; + break; + case 3: + _rawData[3] = vector3D.x; + _rawData[7] = vector3D.y; + _rawData[11] = vector3D.z; + _rawData[15] = vector3D.w; + break; + } + } + + public function deltaTransformVector(v:Vector3D):Vector3D { + var x:Number = this._rawData[0] * v.x + this._rawData[4] * v.y + this._rawData[8] * v.z; + var y:Number = this._rawData[1] * v.x + this._rawData[5] * v.y + this._rawData[9] * v.z; + var z:Number = this._rawData[2] * v.x + this._rawData[6] * v.y + this._rawData[10] * v.z; + var w:Number = this._rawData[3] * v.x + this._rawData[7] * v.y + this._rawData[11] * v.z; + return new Vector3D(x, y, z, w); + } + + public function transformVector(v:Vector3D):Vector3D { + var x:Number = this._rawData[0] * v.x + this._rawData[4] * v.y + this._rawData[8] * v.z + this._rawData[12]; + var y:Number = this._rawData[1] * v.x + this._rawData[5] * v.y + this._rawData[9] * v.z + this._rawData[13]; + var z:Number = this._rawData[2] * v.x + this._rawData[6] * v.y + this._rawData[10] * v.z + this._rawData[14]; + var w:Number = this._rawData[3] * v.x + this._rawData[7] * v.y + this._rawData[11] * v.z + this._rawData[15]; + return new Vector3D(x, y, z, w); + } + + public function transformVectors(vin:Vector., vout:Vector.):void { + if (vin == null) { + throw new TypeError("Error #2007: Parameter vin must be non-null.", 2007); } - if (vout == null) { - throw new TypeError("Error #2007: Parameter vout must be non-null.", 2007); - } - - var resultVecsLength:Number = Math.floor(vin.length / 3) * 3; - if (resultVecsLength > vout.length && vout.fixed) { - throw new RangeError("Error #1126: Cannot change the length of a fixed Vector.") - } - - var result3D:Vector3D; - for (var i = 0; i < resultVecsLength; i += 3) { - result3D = transformVector(new Vector3D(vin[i], vin[i + 1], vin[i + 2])); - if (i <= vout.length) { - vout[i] = result3D.x; - vout[i + 1] = result3D.y; - vout[i + 2] = result3D.z; - } else { - vout.push(result3D.x, result3D.y, result3D.z); - } - } - } - - public function transpose():void { - // Make a copy - var oRawData = this._rawData.concat(); - this._rawData[1] = oRawData[4]; - this._rawData[2] = oRawData[8]; - this._rawData[3] = oRawData[12]; - this._rawData[4] = oRawData[1]; - this._rawData[6] = oRawData[9]; - this._rawData[7] = oRawData[13]; - this._rawData[8] = oRawData[2]; - this._rawData[9] = oRawData[6]; - this._rawData[11] = oRawData[14]; - this._rawData[12] = oRawData[3]; - this._rawData[13] = oRawData[7]; - this._rawData[14] = oRawData[11]; - } - public function append(lhs:Matrix3D):void { - var m111:Number = this._rawData[0], - m121:Number = this._rawData[4], - m131:Number = this._rawData[8], - m141:Number = this._rawData[12], - m112:Number = this._rawData[1], - m122:Number = this._rawData[5], - m132:Number = this._rawData[9], - m142:Number = this._rawData[13], - m113:Number = this._rawData[2], - m123:Number = this._rawData[6], - m133:Number = this._rawData[10], - m143:Number = this._rawData[14], - m114:Number = this._rawData[3], - m124:Number = this._rawData[7], - m134:Number = this._rawData[11], - m144:Number = this._rawData[15], - m211:Number = lhs._rawData[0], - m221:Number = lhs._rawData[4], - m231:Number = lhs._rawData[8], - m241:Number = lhs._rawData[12], - m212:Number = lhs._rawData[1], - m222:Number = lhs._rawData[5], - m232:Number = lhs._rawData[9], - m242:Number = lhs._rawData[13], - m213:Number = lhs._rawData[2], - m223:Number = lhs._rawData[6], - m233:Number = lhs._rawData[10], - m243:Number = lhs._rawData[14], - m214:Number = lhs._rawData[3], - m224:Number = lhs._rawData[7], - m234:Number = lhs._rawData[11], - m244:Number = lhs._rawData[15]; - - this._rawData[0] = m111 * m211 + m112 * m221 + m113 * m231 + m114 * m241; - this._rawData[1] = m111 * m212 + m112 * m222 + m113 * m232 + m114 * m242; - this._rawData[2] = m111 * m213 + m112 * m223 + m113 * m233 + m114 * m243; - this._rawData[3] = m111 * m214 + m112 * m224 + m113 * m234 + m114 * m244; - - this._rawData[4] = m121 * m211 + m122 * m221 + m123 * m231 + m124 * m241; - this._rawData[5] = m121 * m212 + m122 * m222 + m123 * m232 + m124 * m242; - this._rawData[6] = m121 * m213 + m122 * m223 + m123 * m233 + m124 * m243; - this._rawData[7] = m121 * m214 + m122 * m224 + m123 * m234 + m124 * m244; - - this._rawData[8] = m131 * m211 + m132 * m221 + m133 * m231 + m134 * m241; - this._rawData[9] = m131 * m212 + m132 * m222 + m133 * m232 + m134 * m242; - this._rawData[10] = m131 * m213 + m132 * m223 + m133 * m233 + m134 * m243; - this._rawData[11] = m131 * m214 + m132 * m224 + m133 * m234 + m134 * m244; - - this._rawData[12] = m141 * m211 + m142 * m221 + m143 * m231 + m144 * m241; - this._rawData[13] = m141 * m212 + m142 * m222 + m143 * m232 + m144 * m242; - this._rawData[14] = m141 * m213 + m142 * m223 + m143 * m233 + m144 * m243; - this._rawData[15] = m141 * m214 + m142 * m224 + m143 * m234 + m144 * m244; - } - - // Based on https://github.com/openfl/openfl/blob/971a4c9e43b5472fd84d73920a2b7c1b3d8d9257/src/openfl/geom/Matrix3D.hx#L307 - public function appendScale(xScale:Number, yScale:Number, zScale:Number):void { - this.append(new Matrix3D(Vector.([ - xScale, 0.0, 0.0, 0.0, 0.0, yScale, 0.0, 0.0, 0.0, 0.0, zScale, 0.0, 0.0, 0.0, 0.0, 1.0 - ]))); - } - - public function prependTranslation(x:Number, y:Number, z:Number):void { - var m = new Matrix3D(); - m.position = new Vector3D(x, y, z); - this.prepend(m); - } - - public function prependRotation(degrees:Number, axis:Vector3D, pivotPoint:Vector3D = null):void { - var m = new Matrix3D(); - m.appendRotation(degrees, axis, pivotPoint); - this.prepend(m); - } - - public function get position():Vector3D { - return new Vector3D(_rawData[12], _rawData[13], _rawData[14]); - } - - public function set position(val:Vector3D):void { - this._rawData[12] = val.x; - this._rawData[13] = val.y; - this._rawData[14] = val.z; - } - - public function prepend(rhs:Matrix3D):void { - var m111:Number = rhs._rawData[0], - m121:Number = rhs._rawData[4], - m131:Number = rhs._rawData[8], - m141:Number = rhs._rawData[12], - m112:Number = rhs._rawData[1], - m122:Number = rhs._rawData[5], - m132:Number = rhs._rawData[9], - m142:Number = rhs._rawData[13], - m113:Number = rhs._rawData[2], - m123:Number = rhs._rawData[6], - m133:Number = rhs._rawData[10], - m143:Number = rhs._rawData[14], - m114:Number = rhs._rawData[3], - m124:Number = rhs._rawData[7], - m134:Number = rhs._rawData[11], - m144:Number = rhs._rawData[15], - m211:Number = this._rawData[0], - m221:Number = this._rawData[4], - m231:Number = this._rawData[8], - m241:Number = this._rawData[12], - m212:Number = this._rawData[1], - m222:Number = this._rawData[5], - m232:Number = this._rawData[9], - m242:Number = this._rawData[13], - m213:Number = this._rawData[2], - m223:Number = this._rawData[6], - m233:Number = this._rawData[10], - m243:Number = this._rawData[14], - m214:Number = this._rawData[3], - m224:Number = this._rawData[7], - m234:Number = this._rawData[11], - m244:Number = this._rawData[15]; - - this._rawData[0] = m111 * m211 + m112 * m221 + m113 * m231 + m114 * m241; - this._rawData[1] = m111 * m212 + m112 * m222 + m113 * m232 + m114 * m242; - this._rawData[2] = m111 * m213 + m112 * m223 + m113 * m233 + m114 * m243; - this._rawData[3] = m111 * m214 + m112 * m224 + m113 * m234 + m114 * m244; - - this._rawData[4] = m121 * m211 + m122 * m221 + m123 * m231 + m124 * m241; - this._rawData[5] = m121 * m212 + m122 * m222 + m123 * m232 + m124 * m242; - this._rawData[6] = m121 * m213 + m122 * m223 + m123 * m233 + m124 * m243; - this._rawData[7] = m121 * m214 + m122 * m224 + m123 * m234 + m124 * m244; - - this._rawData[8] = m131 * m211 + m132 * m221 + m133 * m231 + m134 * m241; - this._rawData[9] = m131 * m212 + m132 * m222 + m133 * m232 + m134 * m242; - this._rawData[10] = m131 * m213 + m132 * m223 + m133 * m233 + m134 * m243; - this._rawData[11] = m131 * m214 + m132 * m224 + m133 * m234 + m134 * m244; - - this._rawData[12] = m141 * m211 + m142 * m221 + m143 * m231 + m144 * m241; - this._rawData[13] = m141 * m212 + m142 * m222 + m143 * m232 + m144 * m242; - this._rawData[14] = m141 * m213 + m142 * m223 + m143 * m233 + m144 * m243; - this._rawData[15] = m141 * m214 + m142 * m224 + m143 * m234 + m144 * m244; - } - - public function prependScale(xScale:Number, yScale:Number, zScale:Number):void { - var m = new Matrix3D(); - m.appendScale(xScale, yScale, zScale); - this.prepend(m); - } - - [API("674")] - public function copyFrom(other:Matrix3D):void { - // This makes a copy of other.rawData - this._rawData = other.rawData; - } - - [API("674")] - public function copyRawDataTo(vector:Vector., index:uint = 0, transpose:Boolean = false):void { - if (transpose) { - this.transpose(); - } - - for (var i = 0; i < rawData.length; i++) { - vector[i + index] = _rawData[i]; - } - - if (transpose) { - this.transpose(); - } - } - - public function clone():Matrix3D { - return new Matrix3D(this.rawData); - } - - public function copyToMatrix3D(other:Matrix3D):void { - other.rawData = rawData; - } - - public function pointAt(pos:Vector3D, at:Vector3D = null, up:Vector3D = null):void { - stub_method("flash.geom.Matrix3D", "pointAt"); - } - - // Based on OpenFL: https://github.com/openfl/openfl/blob/971a4c9e43b5472fd84d73920a2b7c1b3d8d9257/src/openfl/geom/Matrix3D.hx#L1437 - public function recompose(components:Vector., orientationStyle:String = "eulerAngles"):Boolean { - checkOrientation(orientationStyle); - - if (orientationStyle == Orientation3D.QUATERNION) { - // Flash throws exceptions from 'recompose' certain values of 'components', - // which we need to reproduce. See the 'matrix3d_compose' test - stub_method("flash.geom.Matrix3D", "recompose", "Orientation3D.QUATERNION"); - } - // RUFFLE - unlike in OpenFL, we continue on even if some of the 'scale' components are 0 - if (components.length < 3) { - return false; - } - - identity(); - - var scale = []; - scale[0] = scale[1] = scale[2] = components[2].x; - scale[4] = scale[5] = scale[6] = components[2].y; - scale[8] = scale[9] = scale[10] = components[2].z; - - switch (orientationStyle) { - case Orientation3D.EULER_ANGLES: - var cx = Math.cos(components[1].x); - var cy = Math.cos(components[1].y); - var cz = Math.cos(components[1].z); - var sx = Math.sin(components[1].x); - var sy = Math.sin(components[1].y); - var sz = Math.sin(components[1].z); - - _rawData[0] = cy * cz * scale[0]; - _rawData[1] = cy * sz * scale[1]; - _rawData[2] = -sy * scale[2]; - _rawData[3] = 0; - _rawData[4] = (sx * sy * cz - cx * sz) * scale[4]; - _rawData[5] = (sx * sy * sz + cx * cz) * scale[5]; - _rawData[6] = sx * cy * scale[6]; - _rawData[7] = 0; - _rawData[8] = (cx * sy * cz + sx * sz) * scale[8]; - _rawData[9] = (cx * sy * sz - sx * cz) * scale[9]; - _rawData[10] = cx * cy * scale[10]; - _rawData[11] = 0; - _rawData[12] = components[0].x; - _rawData[13] = components[0].y; - _rawData[14] = components[0].z; - _rawData[15] = 1; - break; - - default: - var x = components[1].x; - var y = components[1].y; - var z = components[1].z; - var w = components[1].w; - - if (orientationStyle == Orientation3D.AXIS_ANGLE) { - x *= Math.sin(w / 2); - y *= Math.sin(w / 2); - z *= Math.sin(w / 2); - w = Math.cos(w / 2); - } - - _rawData[0] = (1 - 2 * y * y - 2 * z * z) * scale[0]; - _rawData[1] = (2 * x * y + 2 * w * z) * scale[1]; - _rawData[2] = (2 * x * z - 2 * w * y) * scale[2]; - _rawData[3] = 0; - _rawData[4] = (2 * x * y - 2 * w * z) * scale[4]; - _rawData[5] = (1 - 2 * x * x - 2 * z * z) * scale[5]; - _rawData[6] = (2 * y * z + 2 * w * x) * scale[6]; - _rawData[7] = 0; - _rawData[8] = (2 * x * z + 2 * w * y) * scale[8]; - _rawData[9] = (2 * y * z - 2 * w * x) * scale[9]; - _rawData[10] = (1 - 2 * x * x - 2 * y * y) * scale[10]; - _rawData[11] = 0; - _rawData[12] = components[0].x; - _rawData[13] = components[0].y; - _rawData[14] = components[0].z; - _rawData[15] = 1; - } - - if (components[2].x == 0) { - _rawData[0] = 1e-15; - } - - if (components[2].y == 0) { - _rawData[5] = 1e-15; - } - - if (components[2].z == 0) { - _rawData[10] = 1e-15; - } - - return !(components[2].x == 0 || components[2].y == 0 || components[2].y == 0); - } - - [API("674")] - public function copyColumnTo(column:uint, vector3D:Vector3D):void { - if (column > 3) { - throw new ArgumentError("Error #2004: One of the parameters is invalid.", 2004); - } - switch (column) { - case 0: - vector3D.x = _rawData[0]; - vector3D.y = _rawData[1]; - vector3D.z = _rawData[2]; - vector3D.w = _rawData[3]; - break; - - case 1: - vector3D.x = _rawData[4]; - vector3D.y = _rawData[5]; - vector3D.z = _rawData[6]; - vector3D.w = _rawData[7]; - break; - - case 2: - vector3D.x = _rawData[8]; - vector3D.y = _rawData[9]; - vector3D.z = _rawData[10]; - vector3D.w = _rawData[11]; - break; - - case 3: - vector3D.x = _rawData[12]; - vector3D.y = _rawData[13]; - vector3D.z = _rawData[14]; - vector3D.w = _rawData[15]; - break; - } - } - - [API("674")] - public function copyColumnFrom(column:uint, vector3D:Vector3D):void { - if (column > 3) { - throw new ArgumentError("Error #2004: One of the parameters is invalid.", 2004); - } - switch (column) { - case 0: - _rawData[0] = vector3D.x; - _rawData[1] = vector3D.y; - _rawData[2] = vector3D.z; - _rawData[3] = vector3D.w; - break; - - case 1: - _rawData[4] = vector3D.x; - _rawData[5] = vector3D.y; - _rawData[6] = vector3D.z; - _rawData[7] = vector3D.w; - break; - - case 2: - _rawData[8] = vector3D.x; - _rawData[9] = vector3D.y; - _rawData[10] = vector3D.z; - _rawData[11] = vector3D.w; - break; - - case 3: - _rawData[12] = vector3D.x; - _rawData[13] = vector3D.y; - _rawData[14] = vector3D.z; - _rawData[15] = vector3D.w; - break; - } - } - - public function decompose(orientationStyle:String = "eulerAngles"):Vector. { - checkOrientation(orientationStyle); - - var vec = new Vector.([]); - var m = clone(); - var mr = m.rawData; - - var pos = new Vector3D(mr[12], mr[13], mr[14]); - mr[12] = 0; - mr[13] = 0; - mr[14] = 0; - - var scale = new Vector3D(); - - scale.x = Math.sqrt(mr[0] * mr[0] + mr[1] * mr[1] + mr[2] * mr[2]); - scale.y = Math.sqrt(mr[4] * mr[4] + mr[5] * mr[5] + mr[6] * mr[6]); - scale.z = Math.sqrt(mr[8] * mr[8] + mr[9] * mr[9] + mr[10] * mr[10]); - - if (mr[0] * (mr[5] * mr[10] - mr[6] * mr[9]) - mr[1] * (mr[4] * mr[10] - mr[6] * mr[8]) + mr[2] * (mr[4] * mr[9] - mr[5] * mr[8]) < 0) { - scale.z = -scale.z; - } - - mr[0] /= scale.x; - mr[1] /= scale.x; - mr[2] /= scale.x; - mr[4] /= scale.y; - mr[5] /= scale.y; - mr[6] /= scale.y; - mr[8] /= scale.z; - mr[9] /= scale.z; - mr[10] /= scale.z; - - var rot = new Vector3D(); - - switch (orientationStyle) { - case Orientation3D.AXIS_ANGLE: - rot.w = Math.acos((mr[0] + mr[5] + mr[10] - 1) / 2); - - var len = Math.sqrt((mr[6] - mr[9]) * (mr[6] - mr[9]) + (mr[8] - mr[2]) * (mr[8] - mr[2]) + (mr[1] - mr[4]) * (mr[1] - mr[4])); - - if (len != 0) { - rot.x = (mr[6] - mr[9]) / len; - rot.y = (mr[8] - mr[2]) / len; - rot.z = (mr[1] - mr[4]) / len; - } - else { - rot.x = rot.y = rot.z = 0; - } - break; - - case Orientation3D.QUATERNION: - var tr = mr[0] + mr[5] + mr[10]; - - if (tr > 0) { - rot.w = Math.sqrt(1 + tr) / 2; - - rot.x = (mr[6] - mr[9]) / (4 * rot.w); - rot.y = (mr[8] - mr[2]) / (4 * rot.w); - rot.z = (mr[1] - mr[4]) / (4 * rot.w); - } - else if ((mr[0] > mr[5]) && (mr[0] > mr[10])) { - rot.x = Math.sqrt(1 + mr[0] - mr[5] - mr[10]) / 2; - - rot.w = (mr[6] - mr[9]) / (4 * rot.x); - rot.y = (mr[1] + mr[4]) / (4 * rot.x); - rot.z = (mr[8] + mr[2]) / (4 * rot.x); - } - else if (mr[5] > mr[10]) { - rot.y = Math.sqrt(1 + mr[5] - mr[0] - mr[10]) / 2; - - rot.x = (mr[1] + mr[4]) / (4 * rot.y); - rot.w = (mr[8] - mr[2]) / (4 * rot.y); - rot.z = (mr[6] + mr[9]) / (4 * rot.y); - } - else { - rot.z = Math.sqrt(1 + mr[10] - mr[0] - mr[5]) / 2; - - rot.x = (mr[8] + mr[2]) / (4 * rot.z); - rot.y = (mr[6] + mr[9]) / (4 * rot.z); - rot.w = (mr[1] - mr[4]) / (4 * rot.z); - } - break; - - case Orientation3D.EULER_ANGLES: - rot.y = Math.asin(-mr[2]); - - if (mr[2] != 1 && mr[2] != -1) { - rot.x = Math.atan2(mr[6], mr[10]); - rot.z = Math.atan2(mr[1], mr[0]); - } - else { - rot.z = 0; - rot.x = Math.atan2(mr[4], mr[5]); - } - break; - } - - vec.push(pos); - vec.push(rot); - vec.push(scale); - - return vec; - } - - public function invert():Boolean { - var d = determinant; - var invertable = Math.abs(d) > 0.00000000001; - - if (invertable) { - d = 1 / d; - - var m11:Number = _rawData[0]; - var m21:Number = _rawData[4]; - var m31:Number = _rawData[8]; - var m41:Number = _rawData[12]; - var m12:Number = _rawData[1]; - var m22:Number = _rawData[5]; - var m32:Number = _rawData[9]; - var m42:Number = _rawData[13]; - var m13:Number = _rawData[2]; - var m23:Number = _rawData[6]; - var m33:Number = _rawData[10]; - var m43:Number = _rawData[14]; - var m14:Number = _rawData[3]; - var m24:Number = _rawData[7]; - var m34:Number = _rawData[11]; - var m44:Number = _rawData[15]; - - _rawData[0] = d * (m22 * (m33 * m44 - m43 * m34) - m32 * (m23 * m44 - m43 * m24) + m42 * (m23 * m34 - m33 * m24)); - _rawData[1] = -d * (m12 * (m33 * m44 - m43 * m34) - m32 * (m13 * m44 - m43 * m14) + m42 * (m13 * m34 - m33 * m14)); - _rawData[2] = d * (m12 * (m23 * m44 - m43 * m24) - m22 * (m13 * m44 - m43 * m14) + m42 * (m13 * m24 - m23 * m14)); - _rawData[3] = -d * (m12 * (m23 * m34 - m33 * m24) - m22 * (m13 * m34 - m33 * m14) + m32 * (m13 * m24 - m23 * m14)); - _rawData[4] = -d * (m21 * (m33 * m44 - m43 * m34) - m31 * (m23 * m44 - m43 * m24) + m41 * (m23 * m34 - m33 * m24)); - _rawData[5] = d * (m11 * (m33 * m44 - m43 * m34) - m31 * (m13 * m44 - m43 * m14) + m41 * (m13 * m34 - m33 * m14)); - _rawData[6] = -d * (m11 * (m23 * m44 - m43 * m24) - m21 * (m13 * m44 - m43 * m14) + m41 * (m13 * m24 - m23 * m14)); - _rawData[7] = d * (m11 * (m23 * m34 - m33 * m24) - m21 * (m13 * m34 - m33 * m14) + m31 * (m13 * m24 - m23 * m14)); - _rawData[8] = d * (m21 * (m32 * m44 - m42 * m34) - m31 * (m22 * m44 - m42 * m24) + m41 * (m22 * m34 - m32 * m24)); - _rawData[9] = -d * (m11 * (m32 * m44 - m42 * m34) - m31 * (m12 * m44 - m42 * m14) + m41 * (m12 * m34 - m32 * m14)); - _rawData[10] = d * (m11 * (m22 * m44 - m42 * m24) - m21 * (m12 * m44 - m42 * m14) + m41 * (m12 * m24 - m22 * m14)); - _rawData[11] = -d * (m11 * (m22 * m34 - m32 * m24) - m21 * (m12 * m34 - m32 * m14) + m31 * (m12 * m24 - m22 * m14)); - _rawData[12] = -d * (m21 * (m32 * m43 - m42 * m33) - m31 * (m22 * m43 - m42 * m23) + m41 * (m22 * m33 - m32 * m23)); - _rawData[13] = d * (m11 * (m32 * m43 - m42 * m33) - m31 * (m12 * m43 - m42 * m13) + m41 * (m12 * m33 - m32 * m13)); - _rawData[14] = -d * (m11 * (m22 * m43 - m42 * m23) - m21 * (m12 * m43 - m42 * m13) + m41 * (m12 * m23 - m22 * m13)); - _rawData[15] = d * (m11 * (m22 * m33 - m32 * m23) - m21 * (m12 * m33 - m32 * m13) + m31 * (m12 * m23 - m22 * m13)); - } - - return invertable; - } - - public function get determinant():Number { - return 1 * ((_rawData[0] * _rawData[5] - _rawData[4] * _rawData[1]) * (_rawData[10] * _rawData[15] - _rawData[14] * _rawData[11]) - - (_rawData[0] * _rawData[9] - _rawData[8] * _rawData[1]) * (_rawData[6] * _rawData[15] - _rawData[14] * _rawData[7]) - + (_rawData[0] * _rawData[13] - _rawData[12] * _rawData[1]) * (_rawData[6] * _rawData[11] - _rawData[10] * _rawData[7]) - + (_rawData[4] * _rawData[9] - _rawData[8] * _rawData[5]) * (_rawData[2] * _rawData[15] - _rawData[14] * _rawData[3]) - - (_rawData[4] * _rawData[13] - _rawData[12] * _rawData[5]) * (_rawData[2] * _rawData[11] - _rawData[10] * _rawData[3]) - + (_rawData[8] * _rawData[13] - _rawData[12] * _rawData[9]) * (_rawData[2] * _rawData[7] - _rawData[6] * _rawData[3])); - } - - } + if (vout == null) { + throw new TypeError("Error #2007: Parameter vout must be non-null.", 2007); + } + + var resultVecsLength:Number = Math.floor(vin.length / 3) * 3; + if (resultVecsLength > vout.length && vout.fixed) { + throw new RangeError("Error #1126: Cannot change the length of a fixed Vector.") + } + + var result3D:Vector3D; + for (var i = 0; i < resultVecsLength; i += 3) { + result3D = transformVector(new Vector3D(vin[i], vin[i + 1], vin[i + 2])); + if (i <= vout.length) { + vout[i] = result3D.x; + vout[i + 1] = result3D.y; + vout[i + 2] = result3D.z; + } else { + vout.push(result3D.x, result3D.y, result3D.z); + } + } + } + + public function transpose():void { + // Make a copy + var oRawData = this._rawData.AS3::concat(); + this._rawData[1] = oRawData[4]; + this._rawData[2] = oRawData[8]; + this._rawData[3] = oRawData[12]; + this._rawData[4] = oRawData[1]; + this._rawData[6] = oRawData[9]; + this._rawData[7] = oRawData[13]; + this._rawData[8] = oRawData[2]; + this._rawData[9] = oRawData[6]; + this._rawData[11] = oRawData[14]; + this._rawData[12] = oRawData[3]; + this._rawData[13] = oRawData[7]; + this._rawData[14] = oRawData[11]; + } + public function append(lhs:Matrix3D):void { + var m111:Number = this._rawData[0], + m121:Number = this._rawData[4], + m131:Number = this._rawData[8], + m141:Number = this._rawData[12], + m112:Number = this._rawData[1], + m122:Number = this._rawData[5], + m132:Number = this._rawData[9], + m142:Number = this._rawData[13], + m113:Number = this._rawData[2], + m123:Number = this._rawData[6], + m133:Number = this._rawData[10], + m143:Number = this._rawData[14], + m114:Number = this._rawData[3], + m124:Number = this._rawData[7], + m134:Number = this._rawData[11], + m144:Number = this._rawData[15], + m211:Number = lhs._rawData[0], + m221:Number = lhs._rawData[4], + m231:Number = lhs._rawData[8], + m241:Number = lhs._rawData[12], + m212:Number = lhs._rawData[1], + m222:Number = lhs._rawData[5], + m232:Number = lhs._rawData[9], + m242:Number = lhs._rawData[13], + m213:Number = lhs._rawData[2], + m223:Number = lhs._rawData[6], + m233:Number = lhs._rawData[10], + m243:Number = lhs._rawData[14], + m214:Number = lhs._rawData[3], + m224:Number = lhs._rawData[7], + m234:Number = lhs._rawData[11], + m244:Number = lhs._rawData[15]; + + this._rawData[0] = m111 * m211 + m112 * m221 + m113 * m231 + m114 * m241; + this._rawData[1] = m111 * m212 + m112 * m222 + m113 * m232 + m114 * m242; + this._rawData[2] = m111 * m213 + m112 * m223 + m113 * m233 + m114 * m243; + this._rawData[3] = m111 * m214 + m112 * m224 + m113 * m234 + m114 * m244; + + this._rawData[4] = m121 * m211 + m122 * m221 + m123 * m231 + m124 * m241; + this._rawData[5] = m121 * m212 + m122 * m222 + m123 * m232 + m124 * m242; + this._rawData[6] = m121 * m213 + m122 * m223 + m123 * m233 + m124 * m243; + this._rawData[7] = m121 * m214 + m122 * m224 + m123 * m234 + m124 * m244; + + this._rawData[8] = m131 * m211 + m132 * m221 + m133 * m231 + m134 * m241; + this._rawData[9] = m131 * m212 + m132 * m222 + m133 * m232 + m134 * m242; + this._rawData[10] = m131 * m213 + m132 * m223 + m133 * m233 + m134 * m243; + this._rawData[11] = m131 * m214 + m132 * m224 + m133 * m234 + m134 * m244; + + this._rawData[12] = m141 * m211 + m142 * m221 + m143 * m231 + m144 * m241; + this._rawData[13] = m141 * m212 + m142 * m222 + m143 * m232 + m144 * m242; + this._rawData[14] = m141 * m213 + m142 * m223 + m143 * m233 + m144 * m243; + this._rawData[15] = m141 * m214 + m142 * m224 + m143 * m234 + m144 * m244; + } + + // Based on https://github.com/openfl/openfl/blob/971a4c9e43b5472fd84d73920a2b7c1b3d8d9257/src/openfl/geom/Matrix3D.hx#L307 + public function appendScale(xScale:Number, yScale:Number, zScale:Number):void { + this.append(new Matrix3D(Vector.([ + xScale, 0.0, 0.0, 0.0, 0.0, yScale, 0.0, 0.0, 0.0, 0.0, zScale, 0.0, 0.0, 0.0, 0.0, 1.0 + ]))); + } + + public function prependTranslation(x:Number, y:Number, z:Number):void { + var m = new Matrix3D(); + m.position = new Vector3D(x, y, z); + this.prepend(m); + } + + public function prependRotation(degrees:Number, axis:Vector3D, pivotPoint:Vector3D = null):void { + var m = new Matrix3D(); + m.appendRotation(degrees, axis, pivotPoint); + this.prepend(m); + } + + public function get position():Vector3D { + return new Vector3D(_rawData[12], _rawData[13], _rawData[14]); + } + + public function set position(val:Vector3D):void { + this._rawData[12] = val.x; + this._rawData[13] = val.y; + this._rawData[14] = val.z; + } + + public function prepend(rhs:Matrix3D):void { + var m111:Number = rhs._rawData[0], + m121:Number = rhs._rawData[4], + m131:Number = rhs._rawData[8], + m141:Number = rhs._rawData[12], + m112:Number = rhs._rawData[1], + m122:Number = rhs._rawData[5], + m132:Number = rhs._rawData[9], + m142:Number = rhs._rawData[13], + m113:Number = rhs._rawData[2], + m123:Number = rhs._rawData[6], + m133:Number = rhs._rawData[10], + m143:Number = rhs._rawData[14], + m114:Number = rhs._rawData[3], + m124:Number = rhs._rawData[7], + m134:Number = rhs._rawData[11], + m144:Number = rhs._rawData[15], + m211:Number = this._rawData[0], + m221:Number = this._rawData[4], + m231:Number = this._rawData[8], + m241:Number = this._rawData[12], + m212:Number = this._rawData[1], + m222:Number = this._rawData[5], + m232:Number = this._rawData[9], + m242:Number = this._rawData[13], + m213:Number = this._rawData[2], + m223:Number = this._rawData[6], + m233:Number = this._rawData[10], + m243:Number = this._rawData[14], + m214:Number = this._rawData[3], + m224:Number = this._rawData[7], + m234:Number = this._rawData[11], + m244:Number = this._rawData[15]; + + this._rawData[0] = m111 * m211 + m112 * m221 + m113 * m231 + m114 * m241; + this._rawData[1] = m111 * m212 + m112 * m222 + m113 * m232 + m114 * m242; + this._rawData[2] = m111 * m213 + m112 * m223 + m113 * m233 + m114 * m243; + this._rawData[3] = m111 * m214 + m112 * m224 + m113 * m234 + m114 * m244; + + this._rawData[4] = m121 * m211 + m122 * m221 + m123 * m231 + m124 * m241; + this._rawData[5] = m121 * m212 + m122 * m222 + m123 * m232 + m124 * m242; + this._rawData[6] = m121 * m213 + m122 * m223 + m123 * m233 + m124 * m243; + this._rawData[7] = m121 * m214 + m122 * m224 + m123 * m234 + m124 * m244; + + this._rawData[8] = m131 * m211 + m132 * m221 + m133 * m231 + m134 * m241; + this._rawData[9] = m131 * m212 + m132 * m222 + m133 * m232 + m134 * m242; + this._rawData[10] = m131 * m213 + m132 * m223 + m133 * m233 + m134 * m243; + this._rawData[11] = m131 * m214 + m132 * m224 + m133 * m234 + m134 * m244; + + this._rawData[12] = m141 * m211 + m142 * m221 + m143 * m231 + m144 * m241; + this._rawData[13] = m141 * m212 + m142 * m222 + m143 * m232 + m144 * m242; + this._rawData[14] = m141 * m213 + m142 * m223 + m143 * m233 + m144 * m243; + this._rawData[15] = m141 * m214 + m142 * m224 + m143 * m234 + m144 * m244; + } + + public function prependScale(xScale:Number, yScale:Number, zScale:Number):void { + var m = new Matrix3D(); + m.appendScale(xScale, yScale, zScale); + this.prepend(m); + } + + [API("674")] + public function copyFrom(other:Matrix3D):void { + // This makes a copy of other.rawData + this._rawData = other.rawData; + } + + [API("674")] + public function copyRawDataTo(vector:Vector., index:uint = 0, transpose:Boolean = false):void { + if (transpose) { + this.transpose(); + } + + for (var i = 0; i < rawData.length; i++) { + vector[i + index] = _rawData[i]; + } + + if (transpose) { + this.transpose(); + } + } + + public function clone():Matrix3D { + return new Matrix3D(this.rawData); + } + + public function copyToMatrix3D(other:Matrix3D):void { + other.rawData = rawData; + } + + public function pointAt(pos:Vector3D, at:Vector3D = null, up:Vector3D = null):void { + stub_method("flash.geom.Matrix3D", "pointAt"); + } + + // Based on OpenFL: https://github.com/openfl/openfl/blob/971a4c9e43b5472fd84d73920a2b7c1b3d8d9257/src/openfl/geom/Matrix3D.hx#L1437 + public function recompose(components:Vector., orientationStyle:String = "eulerAngles"):Boolean { + checkOrientation(orientationStyle); + + if (orientationStyle == Orientation3D.QUATERNION) { + // Flash throws exceptions from 'recompose' certain values of 'components', + // which we need to reproduce. See the 'matrix3d_compose' test + stub_method("flash.geom.Matrix3D", "recompose", "Orientation3D.QUATERNION"); + } + // RUFFLE - unlike in OpenFL, we continue on even if some of the 'scale' components are 0 + if (components.length < 3) { + return false; + } + + identity(); + + var scale = []; + scale[0] = scale[1] = scale[2] = components[2].x; + scale[4] = scale[5] = scale[6] = components[2].y; + scale[8] = scale[9] = scale[10] = components[2].z; + + switch (orientationStyle) { + case Orientation3D.EULER_ANGLES: + var cx = Math.cos(components[1].x); + var cy = Math.cos(components[1].y); + var cz = Math.cos(components[1].z); + var sx = Math.sin(components[1].x); + var sy = Math.sin(components[1].y); + var sz = Math.sin(components[1].z); + + _rawData[0] = cy * cz * scale[0]; + _rawData[1] = cy * sz * scale[1]; + _rawData[2] = -sy * scale[2]; + _rawData[3] = 0; + _rawData[4] = (sx * sy * cz - cx * sz) * scale[4]; + _rawData[5] = (sx * sy * sz + cx * cz) * scale[5]; + _rawData[6] = sx * cy * scale[6]; + _rawData[7] = 0; + _rawData[8] = (cx * sy * cz + sx * sz) * scale[8]; + _rawData[9] = (cx * sy * sz - sx * cz) * scale[9]; + _rawData[10] = cx * cy * scale[10]; + _rawData[11] = 0; + _rawData[12] = components[0].x; + _rawData[13] = components[0].y; + _rawData[14] = components[0].z; + _rawData[15] = 1; + break; + + default: + var x = components[1].x; + var y = components[1].y; + var z = components[1].z; + var w = components[1].w; + + if (orientationStyle == Orientation3D.AXIS_ANGLE) { + x *= Math.sin(w / 2); + y *= Math.sin(w / 2); + z *= Math.sin(w / 2); + w = Math.cos(w / 2); + } + + _rawData[0] = (1 - 2 * y * y - 2 * z * z) * scale[0]; + _rawData[1] = (2 * x * y + 2 * w * z) * scale[1]; + _rawData[2] = (2 * x * z - 2 * w * y) * scale[2]; + _rawData[3] = 0; + _rawData[4] = (2 * x * y - 2 * w * z) * scale[4]; + _rawData[5] = (1 - 2 * x * x - 2 * z * z) * scale[5]; + _rawData[6] = (2 * y * z + 2 * w * x) * scale[6]; + _rawData[7] = 0; + _rawData[8] = (2 * x * z + 2 * w * y) * scale[8]; + _rawData[9] = (2 * y * z - 2 * w * x) * scale[9]; + _rawData[10] = (1 - 2 * x * x - 2 * y * y) * scale[10]; + _rawData[11] = 0; + _rawData[12] = components[0].x; + _rawData[13] = components[0].y; + _rawData[14] = components[0].z; + _rawData[15] = 1; + } + + if (components[2].x == 0) { + _rawData[0] = 1e-15; + } + + if (components[2].y == 0) { + _rawData[5] = 1e-15; + } + + if (components[2].z == 0) { + _rawData[10] = 1e-15; + } + + return !(components[2].x == 0 || components[2].y == 0 || components[2].y == 0); + } + + [API("674")] + public function copyColumnTo(column:uint, vector3D:Vector3D):void { + if (column > 3) { + throw new ArgumentError("Error #2004: One of the parameters is invalid.", 2004); + } + switch (column) { + case 0: + vector3D.x = _rawData[0]; + vector3D.y = _rawData[1]; + vector3D.z = _rawData[2]; + vector3D.w = _rawData[3]; + break; + + case 1: + vector3D.x = _rawData[4]; + vector3D.y = _rawData[5]; + vector3D.z = _rawData[6]; + vector3D.w = _rawData[7]; + break; + + case 2: + vector3D.x = _rawData[8]; + vector3D.y = _rawData[9]; + vector3D.z = _rawData[10]; + vector3D.w = _rawData[11]; + break; + + case 3: + vector3D.x = _rawData[12]; + vector3D.y = _rawData[13]; + vector3D.z = _rawData[14]; + vector3D.w = _rawData[15]; + break; + } + } + + [API("674")] + public function copyColumnFrom(column:uint, vector3D:Vector3D):void { + if (column > 3) { + throw new ArgumentError("Error #2004: One of the parameters is invalid.", 2004); + } + switch (column) { + case 0: + _rawData[0] = vector3D.x; + _rawData[1] = vector3D.y; + _rawData[2] = vector3D.z; + _rawData[3] = vector3D.w; + break; + + case 1: + _rawData[4] = vector3D.x; + _rawData[5] = vector3D.y; + _rawData[6] = vector3D.z; + _rawData[7] = vector3D.w; + break; + + case 2: + _rawData[8] = vector3D.x; + _rawData[9] = vector3D.y; + _rawData[10] = vector3D.z; + _rawData[11] = vector3D.w; + break; + + case 3: + _rawData[12] = vector3D.x; + _rawData[13] = vector3D.y; + _rawData[14] = vector3D.z; + _rawData[15] = vector3D.w; + break; + } + } + + public function decompose(orientationStyle:String = "eulerAngles"):Vector. { + checkOrientation(orientationStyle); + + var vec = new Vector.([]); + var m = clone(); + var mr = m.rawData; + + var pos = new Vector3D(mr[12], mr[13], mr[14]); + mr[12] = 0; + mr[13] = 0; + mr[14] = 0; + + var scale = new Vector3D(); + + scale.x = Math.sqrt(mr[0] * mr[0] + mr[1] * mr[1] + mr[2] * mr[2]); + scale.y = Math.sqrt(mr[4] * mr[4] + mr[5] * mr[5] + mr[6] * mr[6]); + scale.z = Math.sqrt(mr[8] * mr[8] + mr[9] * mr[9] + mr[10] * mr[10]); + + if (mr[0] * (mr[5] * mr[10] - mr[6] * mr[9]) - mr[1] * (mr[4] * mr[10] - mr[6] * mr[8]) + mr[2] * (mr[4] * mr[9] - mr[5] * mr[8]) < 0) { + scale.z = -scale.z; + } + + mr[0] /= scale.x; + mr[1] /= scale.x; + mr[2] /= scale.x; + mr[4] /= scale.y; + mr[5] /= scale.y; + mr[6] /= scale.y; + mr[8] /= scale.z; + mr[9] /= scale.z; + mr[10] /= scale.z; + + var rot = new Vector3D(); + + switch (orientationStyle) { + case Orientation3D.AXIS_ANGLE: + rot.w = Math.acos((mr[0] + mr[5] + mr[10] - 1) / 2); + + var len = Math.sqrt((mr[6] - mr[9]) * (mr[6] - mr[9]) + (mr[8] - mr[2]) * (mr[8] - mr[2]) + (mr[1] - mr[4]) * (mr[1] - mr[4])); + + if (len != 0) { + rot.x = (mr[6] - mr[9]) / len; + rot.y = (mr[8] - mr[2]) / len; + rot.z = (mr[1] - mr[4]) / len; + } + else { + rot.x = rot.y = rot.z = 0; + } + break; + + case Orientation3D.QUATERNION: + var tr = mr[0] + mr[5] + mr[10]; + + if (tr > 0) { + rot.w = Math.sqrt(1 + tr) / 2; + + rot.x = (mr[6] - mr[9]) / (4 * rot.w); + rot.y = (mr[8] - mr[2]) / (4 * rot.w); + rot.z = (mr[1] - mr[4]) / (4 * rot.w); + } + else if ((mr[0] > mr[5]) && (mr[0] > mr[10])) { + rot.x = Math.sqrt(1 + mr[0] - mr[5] - mr[10]) / 2; + + rot.w = (mr[6] - mr[9]) / (4 * rot.x); + rot.y = (mr[1] + mr[4]) / (4 * rot.x); + rot.z = (mr[8] + mr[2]) / (4 * rot.x); + } + else if (mr[5] > mr[10]) { + rot.y = Math.sqrt(1 + mr[5] - mr[0] - mr[10]) / 2; + + rot.x = (mr[1] + mr[4]) / (4 * rot.y); + rot.w = (mr[8] - mr[2]) / (4 * rot.y); + rot.z = (mr[6] + mr[9]) / (4 * rot.y); + } + else { + rot.z = Math.sqrt(1 + mr[10] - mr[0] - mr[5]) / 2; + + rot.x = (mr[8] + mr[2]) / (4 * rot.z); + rot.y = (mr[6] + mr[9]) / (4 * rot.z); + rot.w = (mr[1] - mr[4]) / (4 * rot.z); + } + break; + + case Orientation3D.EULER_ANGLES: + rot.y = Math.asin(-mr[2]); + + if (mr[2] != 1 && mr[2] != -1) { + rot.x = Math.atan2(mr[6], mr[10]); + rot.z = Math.atan2(mr[1], mr[0]); + } + else { + rot.z = 0; + rot.x = Math.atan2(mr[4], mr[5]); + } + break; + } + + vec.push(pos); + vec.push(rot); + vec.push(scale); + + return vec; + } + + public function invert():Boolean { + var d = determinant; + var invertable = Math.abs(d) > 0.00000000001; + + if (invertable) { + d = 1 / d; + + var m11:Number = _rawData[0]; + var m21:Number = _rawData[4]; + var m31:Number = _rawData[8]; + var m41:Number = _rawData[12]; + var m12:Number = _rawData[1]; + var m22:Number = _rawData[5]; + var m32:Number = _rawData[9]; + var m42:Number = _rawData[13]; + var m13:Number = _rawData[2]; + var m23:Number = _rawData[6]; + var m33:Number = _rawData[10]; + var m43:Number = _rawData[14]; + var m14:Number = _rawData[3]; + var m24:Number = _rawData[7]; + var m34:Number = _rawData[11]; + var m44:Number = _rawData[15]; + + _rawData[0] = d * (m22 * (m33 * m44 - m43 * m34) - m32 * (m23 * m44 - m43 * m24) + m42 * (m23 * m34 - m33 * m24)); + _rawData[1] = -d * (m12 * (m33 * m44 - m43 * m34) - m32 * (m13 * m44 - m43 * m14) + m42 * (m13 * m34 - m33 * m14)); + _rawData[2] = d * (m12 * (m23 * m44 - m43 * m24) - m22 * (m13 * m44 - m43 * m14) + m42 * (m13 * m24 - m23 * m14)); + _rawData[3] = -d * (m12 * (m23 * m34 - m33 * m24) - m22 * (m13 * m34 - m33 * m14) + m32 * (m13 * m24 - m23 * m14)); + _rawData[4] = -d * (m21 * (m33 * m44 - m43 * m34) - m31 * (m23 * m44 - m43 * m24) + m41 * (m23 * m34 - m33 * m24)); + _rawData[5] = d * (m11 * (m33 * m44 - m43 * m34) - m31 * (m13 * m44 - m43 * m14) + m41 * (m13 * m34 - m33 * m14)); + _rawData[6] = -d * (m11 * (m23 * m44 - m43 * m24) - m21 * (m13 * m44 - m43 * m14) + m41 * (m13 * m24 - m23 * m14)); + _rawData[7] = d * (m11 * (m23 * m34 - m33 * m24) - m21 * (m13 * m34 - m33 * m14) + m31 * (m13 * m24 - m23 * m14)); + _rawData[8] = d * (m21 * (m32 * m44 - m42 * m34) - m31 * (m22 * m44 - m42 * m24) + m41 * (m22 * m34 - m32 * m24)); + _rawData[9] = -d * (m11 * (m32 * m44 - m42 * m34) - m31 * (m12 * m44 - m42 * m14) + m41 * (m12 * m34 - m32 * m14)); + _rawData[10] = d * (m11 * (m22 * m44 - m42 * m24) - m21 * (m12 * m44 - m42 * m14) + m41 * (m12 * m24 - m22 * m14)); + _rawData[11] = -d * (m11 * (m22 * m34 - m32 * m24) - m21 * (m12 * m34 - m32 * m14) + m31 * (m12 * m24 - m22 * m14)); + _rawData[12] = -d * (m21 * (m32 * m43 - m42 * m33) - m31 * (m22 * m43 - m42 * m23) + m41 * (m22 * m33 - m32 * m23)); + _rawData[13] = d * (m11 * (m32 * m43 - m42 * m33) - m31 * (m12 * m43 - m42 * m13) + m41 * (m12 * m33 - m32 * m13)); + _rawData[14] = -d * (m11 * (m22 * m43 - m42 * m23) - m21 * (m12 * m43 - m42 * m13) + m41 * (m12 * m23 - m22 * m13)); + _rawData[15] = d * (m11 * (m22 * m33 - m32 * m23) - m21 * (m12 * m33 - m32 * m13) + m31 * (m12 * m23 - m22 * m13)); + } + + return invertable; + } + + public function get determinant():Number { + return 1 * ((_rawData[0] * _rawData[5] - _rawData[4] * _rawData[1]) * (_rawData[10] * _rawData[15] - _rawData[14] * _rawData[11]) + - (_rawData[0] * _rawData[9] - _rawData[8] * _rawData[1]) * (_rawData[6] * _rawData[15] - _rawData[14] * _rawData[7]) + + (_rawData[0] * _rawData[13] - _rawData[12] * _rawData[1]) * (_rawData[6] * _rawData[11] - _rawData[10] * _rawData[7]) + + (_rawData[4] * _rawData[9] - _rawData[8] * _rawData[5]) * (_rawData[2] * _rawData[15] - _rawData[14] * _rawData[3]) + - (_rawData[4] * _rawData[13] - _rawData[12] * _rawData[5]) * (_rawData[2] * _rawData[11] - _rawData[10] * _rawData[3]) + + (_rawData[8] * _rawData[13] - _rawData[12] * _rawData[9]) * (_rawData[2] * _rawData[7] - _rawData[6] * _rawData[3])); + } + + } } import flash.geom.Orientation3D; function checkOrientation(orientationStyle:String) { - if (!(orientationStyle == Orientation3D.AXIS_ANGLE || orientationStyle == Orientation3D.EULER_ANGLES || orientationStyle == Orientation3D.QUATERNION)) { - throw new Error("Error #2187: Invalid orientation style " + orientationStyle + ". Value must be one of 'Orientation3D.EULER_ANGLES', 'Orientation3D.AXIS_ANGLE', or 'Orientation3D.QUATERNION'.", 2187); - } + if (!(orientationStyle == Orientation3D.AXIS_ANGLE || orientationStyle == Orientation3D.EULER_ANGLES || orientationStyle == Orientation3D.QUATERNION)) { + throw new Error("Error #2187: Invalid orientation style " + orientationStyle + ". Value must be one of 'Orientation3D.EULER_ANGLES', 'Orientation3D.AXIS_ANGLE', or 'Orientation3D.QUATERNION'.", 2187); + } } From 130638848ea72a7a0fda2e92c53c1527f9e57212 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 6 Oct 2024 21:23:30 -0700 Subject: [PATCH 08/10] avm2: Store `LocalConnection.client` on its native object data --- .../avm2/globals/flash/net/LocalConnection.as | 8 +-- .../globals/flash/net/local_connection.rs | 30 +++++++++ .../avm2/object/local_connection_object.rs | 63 +++++++++++-------- 3 files changed, 71 insertions(+), 30 deletions(-) diff --git a/core/src/avm2/globals/flash/net/LocalConnection.as b/core/src/avm2/globals/flash/net/LocalConnection.as index 39a70d581cf2..b73f22b8b31e 100644 --- a/core/src/avm2/globals/flash/net/LocalConnection.as +++ b/core/src/avm2/globals/flash/net/LocalConnection.as @@ -7,11 +7,8 @@ package flash.net { [Ruffle(InstanceAllocator)] public class LocalConnection extends EventDispatcher { - - public var client: Object; - public function LocalConnection() { - this.client = this; + super(); } [API("667")] @@ -27,6 +24,9 @@ package flash.net { public native function send(connectionName: String, methodName: String, ... arguments):void; + public native function get client():Object; + public native function set client(client:Object):void; + public function allowDomain(... domains): void { stub_method("flash.net.LocalConnection", "allowDomain"); } diff --git a/core/src/avm2/globals/flash/net/local_connection.rs b/core/src/avm2/globals/flash/net/local_connection.rs index 7f055b58901e..c02323209b23 100644 --- a/core/src/avm2/globals/flash/net/local_connection.rs +++ b/core/src/avm2/globals/flash/net/local_connection.rs @@ -119,3 +119,33 @@ pub fn close<'gc>( Ok(Value::Undefined) } + +pub fn get_client<'gc>( + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + if let Some(local_connection) = this.as_local_connection_object() { + Ok(local_connection.client().into()) + } else { + Ok(Value::Undefined) + } +} + +pub fn set_client<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + if let Some(local_connection) = this.as_local_connection_object() { + let client_obj = args.try_get_object(activation, 0); + + if let Some(client_obj) = client_obj { + local_connection.set_client(activation.gc(), client_obj); + } else { + return Err(make_error_2004(activation, Error2004Type::TypeError)); + } + } + + Ok(Value::Undefined) +} diff --git a/core/src/avm2/object/local_connection_object.rs b/core/src/avm2/object/local_connection_object.rs index 3bf5741fe65a..8142a672defa 100644 --- a/core/src/avm2/object/local_connection_object.rs +++ b/core/src/avm2/object/local_connection_object.rs @@ -9,7 +9,8 @@ use crate::local_connection::{LocalConnectionHandle, LocalConnections}; use crate::string::AvmString; use core::fmt; use flash_lso::types::Value as AmfValue; -use gc_arena::{Collect, Gc, GcWeak}; +use gc_arena::barrier::unlock; +use gc_arena::{lock::Lock, Collect, Gc, GcWeak, Mutation}; use std::cell::RefCell; /// A class instance allocator that allocates LocalConnection objects. @@ -19,14 +20,18 @@ pub fn local_connection_allocator<'gc>( ) -> Result, Error<'gc>> { let base = ScriptObjectData::new(class); - Ok(LocalConnectionObject(Gc::new( + let object = LocalConnectionObject(Gc::new( activation.context.gc_context, LocalConnectionObjectData { base, connection_handle: RefCell::new(None), + client: Lock::new(None), }, - )) - .into()) + )); + + object.set_client(activation.gc(), object.into()); + + Ok(object.into()) } #[derive(Clone, Collect, Copy)] @@ -53,6 +58,8 @@ pub struct LocalConnectionObjectData<'gc> { base: ScriptObjectData<'gc>, connection_handle: RefCell>, + + client: Lock>>, } const _: () = assert!(std::mem::offset_of!(LocalConnectionObjectData, base) == 0); @@ -65,6 +72,14 @@ impl<'gc> LocalConnectionObject<'gc> { self.0.connection_handle.borrow().is_some() } + pub fn client(&self) -> Object<'gc> { + self.0.client.get().expect("Client must be initialized") + } + + pub fn set_client(&self, mc: &Mutation<'gc>, client: Object<'gc>) { + unlock!(Gc::write(mc, self.0), LocalConnectionObjectData, client).set(Some(client)); + } + pub fn connect(&self, activation: &mut Activation<'_, 'gc>, name: AvmString<'gc>) -> bool { if self.is_connected() { return false; @@ -119,30 +134,26 @@ impl<'gc> LocalConnectionObject<'gc> { .push(deserialize_value(&mut activation, &argument).unwrap_or(Value::Undefined)); } - if let Ok(client) = self - .get_public_property("client", &mut activation) - .and_then(|v| v.coerce_to_object(&mut activation)) - { - if let Err(e) = client.call_public_property(method_name, &arguments, &mut activation) { - match e { - Error::AvmError(error) => { - if let Ok(event) = activation.avm2().classes().asyncerrorevent.construct( - &mut activation, - &[ - "asyncError".into(), - false.into(), - false.into(), - error, - error, - ], - ) { - Avm2::dispatch_event(activation.context, event, (*self).into()); - } - } - _ => { - tracing::error!("Unhandled error dispatching AVM2 LocalConnection method call to '{method_name}': {e}"); + let client = self.client(); + if let Err(e) = client.call_public_property(method_name, &arguments, &mut activation) { + match e { + Error::AvmError(error) => { + if let Ok(event) = activation.avm2().classes().asyncerrorevent.construct( + &mut activation, + &[ + "asyncError".into(), + false.into(), + false.into(), + error, + error, + ], + ) { + Avm2::dispatch_event(activation.context, event, (*self).into()); } } + _ => { + tracing::error!("Unhandled error dispatching AVM2 LocalConnection method call to '{method_name}': {e}"); + } } } } From 3da12f004f334606010d94ce55fdd57f7597cd1d Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 6 Oct 2024 21:51:31 -0700 Subject: [PATCH 09/10] avm2: Remove three more uses of `coerce_to_object`, used in Stage3D texture handling --- .../src/avm2/globals/flash/display3D/textures/cube_texture.rs | 4 +++- .../globals/flash/display3D/textures/rectangle_texture.rs | 4 +++- core/src/avm2/globals/flash/display3D/textures/texture.rs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/avm2/globals/flash/display3D/textures/cube_texture.rs b/core/src/avm2/globals/flash/display3D/textures/cube_texture.rs index 5271e9e2ca37..2888dff8079e 100644 --- a/core/src/avm2/globals/flash/display3D/textures/cube_texture.rs +++ b/core/src/avm2/globals/flash/display3D/textures/cube_texture.rs @@ -83,7 +83,9 @@ pub fn upload_from_bitmap_data<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(texture) = this.as_texture() { - if let Some(source) = args[0].coerce_to_object(activation)?.as_bitmap_data() { + let source_obj = args.get_object(activation, 0, "source")?; + + if let Some(source) = source_obj.as_bitmap_data() { let side = args[1].coerce_to_u32(activation)?; let mip_level = args[2].coerce_to_u32(activation)?; if mip_level == 0 { diff --git a/core/src/avm2/globals/flash/display3D/textures/rectangle_texture.rs b/core/src/avm2/globals/flash/display3D/textures/rectangle_texture.rs index 0e75264846c0..17fa9351c826 100644 --- a/core/src/avm2/globals/flash/display3D/textures/rectangle_texture.rs +++ b/core/src/avm2/globals/flash/display3D/textures/rectangle_texture.rs @@ -25,7 +25,9 @@ pub fn upload_from_bitmap_data<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(texture) = this.as_texture() { - if let Some(source) = args[0].coerce_to_object(activation)?.as_bitmap_data() { + let source_obj = args.get_object(activation, 0, "source")?; + + if let Some(source) = source_obj.as_bitmap_data() { texture.context3d().copy_bitmapdata_to_texture( source.sync(activation.context.renderer), texture.handle(), diff --git a/core/src/avm2/globals/flash/display3D/textures/texture.rs b/core/src/avm2/globals/flash/display3D/textures/texture.rs index 8588f23ccc8c..8adaeee2715c 100644 --- a/core/src/avm2/globals/flash/display3D/textures/texture.rs +++ b/core/src/avm2/globals/flash/display3D/textures/texture.rs @@ -117,7 +117,9 @@ pub fn upload_from_bitmap_data<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(texture) = this.as_texture() { - if let Some(source) = args[0].coerce_to_object(activation)?.as_bitmap_data() { + let source_obj = args.get_object(activation, 0, "source")?; + + if let Some(source) = source_obj.as_bitmap_data() { let mip_level = args[1].coerce_to_u32(activation)?; if mip_level == 0 { texture.context3d().copy_bitmapdata_to_texture( From d2ca3794987d60b6215f9986f23917784e46afb3 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 6 Oct 2024 22:25:19 -0700 Subject: [PATCH 10/10] avm2: Remove last use of `coerce_to_object` outside of core code --- core/src/avm2/globals/flash/display/display_object.rs | 10 +++++++++- core/src/avm2/globals/flash/geom/transform.rs | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/core/src/avm2/globals/flash/display/display_object.rs b/core/src/avm2/globals/flash/display/display_object.rs index dd630d0f9644..3ed49d2d5bdf 100644 --- a/core/src/avm2/globals/flash/display/display_object.rs +++ b/core/src/avm2/globals/flash/display/display_object.rs @@ -768,7 +768,15 @@ pub fn set_transform<'gc>( // FIXME - consider 3D matrix and pixel bounds let matrix = transform .get_public_property("matrix", activation)? - .coerce_to_object(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() diff --git a/core/src/avm2/globals/flash/geom/transform.rs b/core/src/avm2/globals/flash/geom/transform.rs index a18bdfe30c9b..50f197b4b249 100644 --- a/core/src/avm2/globals/flash/geom/transform.rs +++ b/core/src/avm2/globals/flash/geom/transform.rs @@ -80,6 +80,9 @@ pub fn set_matrix<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { + // TODO: Despite what the docs say, FP accepts a null matrix here, and returns + // 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)?; dobj.set_matrix(activation.context.gc_context, matrix);