diff --git a/core/src/avm1.rs b/core/src/avm1.rs index fee1316f6..844f0ed63 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -25,7 +25,7 @@ mod value; #[cfg(test)] mod tests; -pub use activation::{start_drag, Activation, ActivationIdentifier}; +pub use activation::{Activation, ActivationIdentifier}; pub use debug::VariableDumper; pub use error::Error; pub use function::ExecutionReason; diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index 573ad974d..bf0338762 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -9,7 +9,7 @@ use crate::avm1::{fscommand, globals, scope, ArrayObject, ScriptObject, Value}; use crate::backend::navigator::{NavigationMethod, Request}; use crate::context::UpdateContext; use crate::display_object::{DisplayObject, MovieClip, TDisplayObject, TDisplayObjectContainer}; -use crate::ecma_conversions::f64_to_wrapping_u32; +use crate::ecma_conversions::{f64_to_wrapping_i32, f64_to_wrapping_u32}; use crate::string::{AvmString, SwfStrExt as _, WStr, WString}; use crate::tag_utils::SwfSlice; use crate::vminterface::Instantiator; @@ -24,7 +24,6 @@ use std::cmp::min; use std::fmt; use swf::avm1::read::Reader; use swf::avm1::types::*; -use swf::{Rectangle, Twips}; use url::form_urlencoded; use super::object_reference::MovieClipReference; @@ -1004,9 +1003,9 @@ impl<'a, 'gc> Activation<'a, 'gc> { } fn action_end_drag(&mut self) -> Result, Error<'gc>> { - // we might not have had an opportunity to call `update_drag` - // if AS did `startDrag(mc);stopDrag();` in one go - // so let's do it here + // We might not have had an opportunity to call `update_drag` + // if AS did `startDrag(mc); stopDrag();` in one go, + // so let's do it here. crate::player::Player::update_drag(&mut self.context); *self.context.drag_object = None; @@ -1126,21 +1125,38 @@ impl<'a, 'gc> Activation<'a, 'gc> { } fn action_get_property(&mut self) -> Result, Error<'gc>> { - let prop_value = self.context.avm1.pop(); - let prop_index = prop_value.coerce_to_f64(self)?; + let prop_index = self.context.avm1.pop().coerce_to_f64(self)?; let path = self.context.avm1.pop(); - let result = if prop_index.is_nan() || prop_index <= -1.0 { - avm_warn!(self, "GetProperty: Invalid property {:?}", prop_value); - Value::Undefined - } else if let Some(target) = self.target_clip() { - let prop_index = prop_index as usize; - if let Some(clip) = self.resolve_target_display_object(target, path, true)? { - let display_properties = self.context.avm1.display_properties(); - let props = display_properties.read(); - if let Some(property) = props.get_by_index(prop_index) { + + let (target, clip) = if let Some(target) = self.target_clip() { + ( + Some(target), + self.resolve_target_display_object(target, path, true)?, + ) + } else { + // `path` must be coerced even if the target is invalid. + let _ = path.coerce_to_string(self)?; + (None, None) + }; + + let property = if !prop_index.is_finite() || prop_index <= -1.0 { + None + } else { + let display_properties = self.context.avm1.display_properties(); + let properties = display_properties.read(); + if let Some(property) = properties.get_by_index(prop_index as usize) { + Some(*property) + } else { + None + } + }; + + let result = if target.is_some() { + if let Some(clip) = clip { + if let Some(property) = property { property.get(self, clip) } else { - avm_warn!(self, "GetProperty: Invalid property index {}", prop_index); + avm_warn!(self, "GetProperty: Invalid property {}", prop_index); Value::Undefined } } else { @@ -1151,6 +1167,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { avm_warn!(self, "GetProperty: Invalid base clip"); Value::Undefined }; + self.stack_push(result); Ok(FrameControl::Continue) } @@ -1191,7 +1208,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let level = self.resolve_level(level_id); if url.is_empty() { - //Blank URL on movie loads = unload! + // Blank URL on movie loads = unload! if let Some(mc) = level.as_movie_clip() { mc.avm1_unload(&mut self.context); mc.replace_with_movie(&mut self.context, None, None) @@ -1644,9 +1661,9 @@ impl<'a, 'gc> Activation<'a, 'gc> { } fn action_multiply(&mut self) -> Result, Error<'gc>> { - let a = self.context.avm1.pop().coerce_to_f64(self)?; - let b = self.context.avm1.pop().coerce_to_f64(self)?; - let result = b * a; + let a = self.context.avm1.pop(); + let b = self.context.avm1.pop(); + let result = b.coerce_to_f64(self)? * a.coerce_to_f64(self)?; self.context.avm1.push(result.into()); Ok(FrameControl::Continue) } @@ -1878,22 +1895,57 @@ impl<'a, 'gc> Activation<'a, 'gc> { } fn action_set_property(&mut self) -> Result, Error<'gc>> { - let value = self.context.avm1.pop(); - let prop_index = self.context.avm1.pop().coerce_to_u32(self)? as usize; + let prop_value = self.context.avm1.pop(); + let prop_index = self.context.avm1.pop().coerce_to_f64(self)?; let path = self.context.avm1.pop(); - if let Some(target) = self.target_clip() { - if let Some(clip) = self.resolve_target_display_object(target, path, true)? { - let display_properties = self.context.avm1.display_properties(); - let props = display_properties.read(); - if let Some(property) = props.get_by_index(prop_index) { - property.set(self, clip, value)?; + + let (target, clip) = if let Some(target) = self.target_clip() { + ( + Some(target), + self.resolve_target_display_object(target, path, true)?, + ) + } else { + // `path` must be coerced even if the target is invalid. + let _ = path.coerce_to_string(self)?; + (None, None) + }; + + let property = if !prop_index.is_finite() || prop_index <= -1.0 { + None + } else { + let display_properties = self.context.avm1.display_properties(); + let properties = display_properties.read(); + if let Some(property) = properties.get_by_index(prop_index as usize) { + if clip.is_none() || property.is_read_only() { + // `prop_value` must be coerced even if the target is invalid or the property is read-only. + // This behavior is consistent since Flash Player 21. Previous versions usually only coerce + // when valid data is provided, but Flash Player 19 and 20 make no coercion *at all*. + let _ = crate::avm1::object::stage_object::action_property_coerce( + self, + prop_index as usize, + prop_value, + ); + } + Some(*property) + } else { + None + } + }; + + if target.is_some() { + if let Some(clip) = clip { + if let Some(property) = property { + property.set(self, clip, prop_value)?; + } else { + avm_warn!(self, "SetProperty: Invalid property {}", prop_index); } } else { - avm_warn!(self, "SetProperty: Invalid target"); + avm_warn!(self, "SetProperty: Invalid target {:?}", path); } } else { avm_warn!(self, "SetProperty: Invalid base clip"); - } + }; + Ok(FrameControl::Continue) } @@ -1988,21 +2040,30 @@ impl<'a, 'gc> Activation<'a, 'gc> { let target = self.context.avm1.pop(); let start_clip = self.target_clip_or_root(); let display_object = self.resolve_target_display_object(start_clip, target, true)?; + + let lock_center = self.context.avm1.pop().coerce_to_i32(self)? == 1; + let constrain = self.context.avm1.pop().coerce_to_i32(self)? == 1; + let constraint_args = if constrain { + let y_max = self.context.avm1.pop().coerce_to_f64(self)?; + let x_max = self.context.avm1.pop().coerce_to_f64(self)?; + let y_min = self.context.avm1.pop().coerce_to_f64(self)?; + let x_min = self.context.avm1.pop().coerce_to_f64(self)?; + Some([x_min, y_min, x_max, y_max]) + } else { + None + }; + if let Some(display_object) = display_object { - let lock_center = self.context.avm1.pop(); - let constrain = self.context.avm1.pop().as_bool(self.swf_version()); - if constrain { - let y2 = self.context.avm1.pop(); - let x2 = self.context.avm1.pop(); - let y1 = self.context.avm1.pop(); - let x1 = self.context.avm1.pop(); - start_drag(display_object, self, &[lock_center, x1, y1, x2, y2]); - } else { - start_drag(display_object, self, &[lock_center]); - }; + globals::movie_clip::start_drag_impl( + display_object, + self, + lock_center, + constraint_args, + ); } else { avm_warn!(self, "StartDrag: Invalid target"); } + Ok(FrameControl::Continue) } @@ -2038,22 +2099,18 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn action_string_add(&mut self) -> Result, Error<'gc>> { // SWFv4 string concatenation // TODO(Herschel): Result with non-string operands? - let a = self.context.avm1.pop(); - let b = self.context.avm1.pop(); - let s = AvmString::concat( - self.context.gc_context, - b.coerce_to_string(self)?, - a.coerce_to_string(self)?, - ); + let a = self.context.avm1.pop().coerce_to_string(self)?; + let b = self.context.avm1.pop().coerce_to_string(self)?; + let s = AvmString::concat(self.context.gc_context, b, a); self.context.avm1.push(s.into()); Ok(FrameControl::Continue) } fn action_string_equals(&mut self) -> Result, Error<'gc>> { // AS1 strcmp - let a = self.context.avm1.pop(); - let b = self.context.avm1.pop(); - let result = b.coerce_to_string(self)? == a.coerce_to_string(self)?; + let a = self.context.avm1.pop().coerce_to_string(self)?; + let b = self.context.avm1.pop().coerce_to_string(self)?; + let result = b == a; self.context.avm1.push(result.into()); // Diverges from spec: returns a boolean even in SWF 4 Ok(FrameControl::Continue) } @@ -2102,9 +2159,9 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn action_string_less(&mut self) -> Result, Error<'gc>> { // AS1 strcmp - let a = self.context.avm1.pop(); - let b = self.context.avm1.pop(); - let result = b.coerce_to_string(self)?.lt(&a.coerce_to_string(self)?); + let a = self.context.avm1.pop().coerce_to_string(self)?; + let b = self.context.avm1.pop().coerce_to_string(self)?; + let result = b.lt(&a); self.context.avm1.push(result.into()); // Diverges from spec: returns a boolean even in SWF 4 Ok(FrameControl::Continue) } @@ -2277,17 +2334,38 @@ impl<'a, 'gc> Activation<'a, 'gc> { action: WaitForFrame2, r: &mut Reader<'_>, ) -> Result, Error<'gc>> { - let frame_num = self.context.avm1.pop().coerce_to_f64(self)? as u16; - let loaded = self - .target_clip() - .and_then(|dobj| dobj.as_movie_clip()) - .map(|mc| mc.frames_loaded() > min(frame_num, mc.total_frames() - 1)) - .unwrap_or(true); + let frame_val = self.context.avm1.pop(); + let frame_num = match frame_val { + Value::Number(n) if n.fract() == 0.0 => f64_to_wrapping_i32(n), + val => { + // The SWF19 spec says that the frame is evaluated in the same way as `Action::GotoFrame2`. + // Though this may be true, this doesn't seem to work in practice for things like a label + // name, as it is not possible to verify its existence if the related frame is not loaded + // yet. In this situation, this action will consider the frame as loaded. + let frame_str = val.coerce_to_string(self)?; + match frame_str.parse::() { + Ok(n) if n.fract() == 0.0 => f64_to_wrapping_i32(n), + _ => 0, + } + } + }; + + let loaded = if frame_num > 16001 { + // Exceeded maximum number of frames. + false + } else { + self.target_clip() + .and_then(|dobj| dobj.as_movie_clip()) + .map(|mc| mc.frames_loaded() > min(frame_num as u16, mc.total_frames() - 1)) + .unwrap_or(true) + }; + if !loaded { // Note that the offset is given in # of actions, NOT in bytes. // Read the actions and toss them away. skip_actions(r, action.num_actions_to_skip); } + Ok(FrameControl::Continue) } @@ -3030,73 +3108,3 @@ impl<'a, 'gc> Activation<'a, 'gc> { Ok(FrameControl::Continue) } } - -/// Starts dragging this display object, making it follow the cursor. -/// Runs via the `startDrag` method or `StartDrag` AVM1 action. -pub fn start_drag<'gc>( - display_object: DisplayObject<'gc>, - activation: &mut Activation<'_, 'gc>, - args: &[Value<'gc>], -) { - let lock_center = args - .get(0) - .map(|o| o.as_bool(activation.context.swf.version())) - .unwrap_or(false); - - let constraint = if args.len() > 1 { - // Invalid values turn into 0. - let mut x_min = args - .get(1) - .unwrap_or(&Value::Undefined) - .coerce_to_f64(activation) - .map(|n| if n.is_finite() { n } else { 0.0 }) - .map(Twips::from_pixels) - .unwrap_or_default(); - let mut y_min = args - .get(2) - .unwrap_or(&Value::Undefined) - .coerce_to_f64(activation) - .map(|n| if n.is_finite() { n } else { 0.0 }) - .map(Twips::from_pixels) - .unwrap_or_default(); - let mut x_max = args - .get(3) - .unwrap_or(&Value::Undefined) - .coerce_to_f64(activation) - .map(|n| if n.is_finite() { n } else { 0.0 }) - .map(Twips::from_pixels) - .unwrap_or_default(); - let mut y_max = args - .get(4) - .unwrap_or(&Value::Undefined) - .coerce_to_f64(activation) - .map(|n| if n.is_finite() { n } else { 0.0 }) - .map(Twips::from_pixels) - .unwrap_or_default(); - - // Normalize the bounds. - if x_max.get() < x_min.get() { - std::mem::swap(&mut x_min, &mut x_max); - } - if y_max.get() < y_min.get() { - std::mem::swap(&mut y_min, &mut y_max); - } - Rectangle { - x_min, - y_min, - x_max, - y_max, - } - } else { - // No constraints. - Default::default() - }; - - let drag_object = crate::player::DragObject { - display_object, - last_mouse_position: *activation.context.mouse_position, - lock_center, - constraint, - }; - *activation.context.drag_object = Some(drag_object); -} diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 87144f3b0..347201270 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -1058,10 +1058,95 @@ fn start_drag<'gc>( activation: &mut Activation<'_, 'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - crate::avm1::activation::start_drag(movie_clip.into(), activation, args); + let lock_center = args + .get(0) + .map(|o| o.as_bool(activation.context.swf.version())) + .unwrap_or(false); + + let constraint_args = if args.len() > 1 { + let x_min = args + .get(1) + .unwrap_or(&Value::Undefined) + .coerce_to_f64(activation)?; + let y_min = args + .get(2) + .unwrap_or(&Value::Undefined) + .coerce_to_f64(activation)?; + let x_max = args + .get(3) + .unwrap_or(&Value::Undefined) + .coerce_to_f64(activation)?; + let y_max = args + .get(4) + .unwrap_or(&Value::Undefined) + .coerce_to_f64(activation)?; + Some([x_min, y_min, x_max, y_max]) + } else { + None + }; + + start_drag_impl(movie_clip.into(), activation, lock_center, constraint_args); + Ok(Value::Undefined) } +pub fn start_drag_impl<'gc>( + display_object: DisplayObject<'gc>, + activation: &mut Activation<'_, 'gc>, + lock_center: bool, + constraint_args: Option<[f64; 4]>, +) { + let constraint = if let Some(constraint_args) = constraint_args { + // Invalid values turn into 0. + let mut x_min = Twips::from_pixels(if constraint_args[0].is_finite() { + constraint_args[0] + } else { + 0.0 + }); + let mut y_min = Twips::from_pixels(if constraint_args[1].is_finite() { + constraint_args[1] + } else { + 0.0 + }); + let mut x_max = Twips::from_pixels(if constraint_args[2].is_finite() { + constraint_args[2] + } else { + 0.0 + }); + let mut y_max = Twips::from_pixels(if constraint_args[3].is_finite() { + constraint_args[3] + } else { + 0.0 + }); + + // Normalize the bounds. + if x_max.get() < x_min.get() { + std::mem::swap(&mut x_min, &mut x_max); + } + if y_max.get() < y_min.get() { + std::mem::swap(&mut y_min, &mut y_max); + } + + Rectangle { + x_min, + y_min, + x_max, + y_max, + } + } else { + // No constraints. + Default::default() + }; + + let drag_object = crate::player::DragObject { + display_object, + last_mouse_position: *activation.context.mouse_position, + lock_center, + constraint, + }; + *activation.context.drag_object = Some(drag_object); +} + fn stop<'gc>( movie_clip: MovieClip<'gc>, activation: &mut Activation<'_, 'gc>, @@ -1078,9 +1163,9 @@ fn stop_drag<'gc>( ) -> Result, Error<'gc>> { // It doesn't matter which clip we call this on; it simply stops any active drag. - // we might not have had an opportunity to call `update_drag` - // if AS did `startDrag(mc);stopDrag();` in one go - // so let's do it here + // We might not have had an opportunity to call `update_drag` + // if AS did `startDrag(mc); stopDrag();` in one go, + // so let's do it here. crate::player::Player::update_drag(&mut activation.context); *activation.context.drag_object = None; diff --git a/core/src/avm1/object/stage_object.rs b/core/src/avm1/object/stage_object.rs index 20419dbdc..ea1b98aa8 100644 --- a/core/src/avm1/object/stage_object.rs +++ b/core/src/avm1/object/stage_object.rs @@ -391,6 +391,10 @@ impl<'gc> DisplayProperty { } Ok(()) } + + pub fn is_read_only(&self) -> bool { + self.set.is_none() + } } /// The map from key/index to function pointers for special display object properties. @@ -790,7 +794,35 @@ fn property_coerce_to_number<'gc>( return Ok(Some(n)); } } - // Invalid value; do not set. Ok(None) } + +/// Coerces a value according to the property index. +/// Used by `SetProperty`. +pub fn action_property_coerce<'gc>( + activation: &mut Activation<'_, 'gc>, + index: usize, + value: Value<'gc>, +) -> Result, Error<'gc>> { + Ok(match index { + // Coerce to a number. This affects the following properties (including some which have no setter): + // _x, _y, _xscale, _yscale, _currentframe, _totalframes, _alpha, _visible, _width, _height, _rotation, _framesloaded. + 0..=10 | 12 => { + if let Some(value_to_number) = property_coerce_to_number(activation, value)? { + value_to_number.into() + } else { + value + } + } + // Coerce to a f64. This affects the following properties (including some which have no setter): + // _highquality, _soundbuftime, _xmouse, _ymouse. + 16 | 18 | 20..=21 => value.coerce_to_f64(activation)?.into(), + // Coerce to a string. This affects the following properties: + // _name, _quality. + 13 | 19 => value.coerce_to_string(activation)?.into(), + // No coercion. This affects the following properties: + // _target, _droptarget, _url, _focusrect. + _ => value, + }) +} diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index 70bc0dae2..492005e7e 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -240,9 +240,9 @@ pub fn stop_drag<'gc>( ) -> Result, Error<'gc>> { // It doesn't matter which clip we call this on; it simply stops any active drag. - // we might not have had an opportunity to call `update_drag` - // if AS did `startDrag(mc);stopDrag();` in one go - // so let's do it here + // We might not have had an opportunity to call `update_drag` + // if AS did `startDrag(mc); stopDrag();` in one go, + // so let's do it here. crate::player::Player::update_drag(&mut activation.context); *activation.context.drag_object = None; diff --git a/tests/tests/swfs/avm1/swf4_actions_coercion_order/output.txt b/tests/tests/swfs/avm1/swf4_actions_coercion_order/output.txt new file mode 100644 index 000000000..f21a65b43 --- /dev/null +++ b/tests/tests/swfs/avm1/swf4_actions_coercion_order/output.txt @@ -0,0 +1,159 @@ +// Add +obj_1.valueOf() +obj_2.valueOf() +3 + +// Subtract +obj_2.valueOf() +obj_1.valueOf() +-1 + +// Multiply +obj_1.valueOf() +obj_2.valueOf() +2 + +// Divide +obj_2.valueOf() +obj_1.valueOf() +0.5 + +// Equals +obj_2.valueOf() +obj_1.valueOf() +false + +// Less +obj_1.valueOf() +obj_2.valueOf() +true + +// And +true + +// Or +true + +// Not +false + +// StringEquals +obj_2.toString() +obj_1.toString() +true + +// StringLength +obj_2.toString() +13 + +// StringAdd +obj_2.toString() +obj_1.toString() +[type Object][type Object] + +// StringExtract +obj_3.valueOf() +obj_2.valueOf() +obj_1.toString() +typ + +// StringLess +obj_2.toString() +obj_1.toString() +false + +// MBStringLength +obj_2.toString() +13 + +// MBStringExtract +obj_3.valueOf() +obj_2.valueOf() +obj_1.toString() +typ + +// ToInteger +obj_2.valueOf() +2 + +// CharToAscii +obj_2.toString() +91 + +// AsciiToChar +obj_2.valueOf() + + +// MBCharToAscii +obj_2.toString() +91 + +// MBAsciiToChar +obj_2.valueOf() + + +// Call +obj_2.toString() + +// GetURL2 +obj_2.toString() +obj_1.toString() + +// GotoFrame2 +obj_2.toString() + +// SetTarget2 +obj_2.toString() +Target not found: Target="[type Object]" Base="_level0" +tellTarget + +// GetProperty +obj_7.valueOf() +obj_0.toString() +undefined + +// SetProperty +obj_7.valueOf() +obj_0.toString() +obj_1.valueOf() + +obj_0.toString() +obj_2.valueOf() + +obj_0.toString() +obj_3.valueOf() + +obj_0.toString() +obj_4.toString() + +obj_0.toString() + +// CloneSprite +obj_3.valueOf() +obj_2.toString() +obj_1.toString() + +// RemoveSprite +obj_1.toString() + +// StartDrag +obj_0.toString() +obj_1.valueOf() +obj_7.valueOf() + +obj_0.toString() +obj_2.valueOf() +obj_1.valueOf() +obj_6.valueOf() +obj_5.valueOf() +obj_4.valueOf() +obj_3.valueOf() + +// WaitForFrame2 +obj_1.toString() +ifFrameLoaded + +// RandomNumber +obj_0.valueOf() +0 + diff --git a/tests/tests/swfs/avm1/swf4_actions_coercion_order/source.txt b/tests/tests/swfs/avm1/swf4_actions_coercion_order/source.txt new file mode 100644 index 000000000..68a7df25b --- /dev/null +++ b/tests/tests/swfs/avm1/swf4_actions_coercion_order/source.txt @@ -0,0 +1,167 @@ +// SWF hand-edited with JPEXS. + + +function newObject(index) { + var object = "obj_" + index; + var valueOf = (i === 0 ? -1 : index); + var toString = (i === 0 ? "/" : index.toString()); + _root[object] = { + valueOf: function() { + trace(object + ".valueOf()"); + return toString; + }, + toString: function() { + trace(object + ".toString()"); + return valueOf; + } + }; +} + +var i = 0; +for(; i < 8; i++) { + newObject(i); +} + + +trace("// Add"); +trace(obj_1 + obj_2); // JPEXS usually compiles to `Add2`. +trace(""); + +trace("// Subtract"); +trace(obj_1 - obj_2); +trace(""); + +trace("// Multiply"); +trace(obj_1 * obj_2); +trace(""); + +trace("// Divide"); +trace(obj_1 / obj_2); +trace(""); + +trace("// Equals"); +trace(obj_1 == obj_2); // JPEXS usually compiles to `Equals2`. +trace(""); + +trace("// Less"); +trace(obj_1 < obj_2); // JPEXS usually compiles to `Less2`. +trace(""); + +trace("// And"); +trace(obj_2 and obj_1); +trace(""); + +trace("// Or"); +trace(obj_1 or obj_2); +trace(""); + +trace("// Not"); +trace(!obj_1); +trace(""); + +trace("// StringEquals"); +trace(obj_1 eq obj_2); +trace(""); + +trace("// StringLength"); +trace(length(obj_2)); +trace(""); + +trace("// StringAdd"); +trace(obj_1 add obj_2); +trace(""); + +trace("// StringExtract"); +trace(substr(obj_1, obj_2, obj_3)); +trace(""); + +trace("// StringLess"); +trace(obj_1 lt obj_2); +trace(""); + +trace("// MBStringLength"); +trace(mblength(obj_2)); +trace(""); + +trace("// MBStringExtract"); +trace(mbsubstring(obj_1, obj_2, obj_3)); +trace(""); + +trace("// ToInteger"); +trace(int(obj_2)); +trace(""); + +trace("// CharToAscii"); +trace(ord(obj_2)); +trace(""); + +trace("// AsciiToChar"); +trace(chr(obj_2)); +trace(""); + +trace("// MBCharToAscii"); +trace(mbord(obj_2)); +trace(""); + +trace("// MBAsciiToChar"); +trace(mbchr(obj_2)); +trace(""); + +trace("// Call"); +call(obj_2); +trace(""); + +trace("// GetURL2"); +loadVariables(obj_1, obj_2, "GET"); +trace(""); + +trace("// GotoFrame2"); +gotoAndStop(obj_2); +trace(""); + +trace("// SetTarget2"); +tellTarget(obj_2) { + trace("tellTarget"); +} +trace(""); + +trace("// GetProperty"); +trace(getProperty(obj_0, obj_7)); // P-code edited to make the property name use the variable "obj_7". +trace(""); + +trace("// SetProperty"); +setProperty(obj_0, obj_7, obj_1); // P-code edited to make the property name use the variable "obj_7". +trace(""); +setProperty(obj_0, _totalframes, obj_2); +trace(""); +setProperty(obj_0, _xmouse, obj_3); +trace(""); +setProperty(obj_0, _name, obj_4); +trace(""); +setProperty(obj_0, _url, obj_5); +trace(""); + +trace("// CloneSprite"); +duplicateMovieClip(obj_1, obj_2, obj_3); +trace(""); + +trace("// RemoveSprite"); +removeMovieClip(obj_1); +trace(""); + +trace("// StartDrag"); +startDrag(obj_0, obj_1, obj_2, obj_3, obj_4, obj_5); // P-code must be edited to make the constraint use the variable "obj_7". +trace(""); +startDrag(obj_0, obj_2, obj_3, obj_4, obj_5, obj_6); // P-code must be edited to make the constraint use the variable "obj_1". +trace(""); + +trace("// WaitForFrame2"); +ifFrameLoaded(obj_1) { + trace("ifFrameLoaded"); +} +trace(""); + +trace("// RandomNumber"); +trace(random(obj_0)); +trace(""); + diff --git a/tests/tests/swfs/avm1/swf4_actions_coercion_order/test.swf b/tests/tests/swfs/avm1/swf4_actions_coercion_order/test.swf new file mode 100644 index 000000000..b4e718cdb Binary files /dev/null and b/tests/tests/swfs/avm1/swf4_actions_coercion_order/test.swf differ diff --git a/tests/tests/swfs/avm1/swf4_actions_coercion_order/test.toml b/tests/tests/swfs/avm1/swf4_actions_coercion_order/test.toml new file mode 100644 index 000000000..dbee897f5 --- /dev/null +++ b/tests/tests/swfs/avm1/swf4_actions_coercion_order/test.toml @@ -0,0 +1 @@ +num_frames = 1