From 4d12e0e5b41694895f4a49c4aca56ffbdf87adbe Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Fri, 31 May 2024 22:08:53 +0200 Subject: [PATCH] core: Implement LocalConnection --- core/src/avm1/globals.rs | 2 +- core/src/avm1/globals/local_connection.rs | 240 ++++- core/src/avm1/object.rs | 2 + core/src/avm2/amf.rs | 35 +- core/src/avm2/error.rs | 17 + core/src/avm2/globals.rs | 3 + .../avm2/globals/flash/net/LocalConnection.as | 16 +- .../globals/flash/net/local_connection.rs | 107 ++- .../avm2/object/local_connection_object.rs | 102 +- core/src/local_connection.rs | 239 ++++- core/src/player.rs | 1 + .../localconnection/CustomLocalConnection.as | 15 + .../avm1/localconnection/avm1child/child.fla | Bin 0 -> 5487 bytes .../avm1/localconnection/avm1child/child.swf | Bin 0 -> 1383 bytes .../avm1/localconnection/avm2child/Child.as | 111 +++ .../avm1/localconnection/avm2child/child.fla | Bin 0 -> 4574 bytes .../avm1/localconnection/avm2child/child.swf | Bin 0 -> 1737 bytes .../swfs/avm1/localconnection/output.txt | 580 ++++++++++++ .../tests/swfs/avm1/localconnection/test.fla | Bin 0 -> 7303 bytes .../tests/swfs/avm1/localconnection/test.swf | Bin 0 -> 3386 bytes .../tests/swfs/avm1/localconnection/test.toml | 1 + .../localconnection_properties/output.txt | 8 + .../avm1/localconnection_properties/test.fla | Bin 0 -> 4859 bytes .../avm1/localconnection_properties/test.swf | Bin 0 -> 911 bytes .../avm1/localconnection_properties/test.toml | 1 + .../localconnection/CustomLocalConnection.as | 26 + tests/tests/swfs/avm2/localconnection/Test.as | 449 +++++++++ .../avm2/localconnection/avm1child/child.fla | Bin 0 -> 5487 bytes .../avm2/localconnection/avm1child/child.swf | Bin 0 -> 1383 bytes .../avm2/localconnection/avm2child/Child.as | 111 +++ .../avm2/localconnection/avm2child/child.fla | Bin 0 -> 4574 bytes .../avm2/localconnection/avm2child/child.swf | Bin 0 -> 1737 bytes .../swfs/avm2/localconnection/output.txt | 890 ++++++++++++++++++ .../tests/swfs/avm2/localconnection/test.fla | Bin 0 -> 4663 bytes .../tests/swfs/avm2/localconnection/test.swf | Bin 0 -> 4639 bytes .../tests/swfs/avm2/localconnection/test.toml | 1 + 36 files changed, 2792 insertions(+), 165 deletions(-) create mode 100644 tests/tests/swfs/avm1/localconnection/CustomLocalConnection.as create mode 100644 tests/tests/swfs/avm1/localconnection/avm1child/child.fla create mode 100644 tests/tests/swfs/avm1/localconnection/avm1child/child.swf create mode 100644 tests/tests/swfs/avm1/localconnection/avm2child/Child.as create mode 100644 tests/tests/swfs/avm1/localconnection/avm2child/child.fla create mode 100644 tests/tests/swfs/avm1/localconnection/avm2child/child.swf create mode 100644 tests/tests/swfs/avm1/localconnection/output.txt create mode 100644 tests/tests/swfs/avm1/localconnection/test.fla create mode 100644 tests/tests/swfs/avm1/localconnection/test.swf create mode 100644 tests/tests/swfs/avm1/localconnection/test.toml create mode 100644 tests/tests/swfs/avm1/localconnection_properties/output.txt create mode 100644 tests/tests/swfs/avm1/localconnection_properties/test.fla create mode 100644 tests/tests/swfs/avm1/localconnection_properties/test.swf create mode 100644 tests/tests/swfs/avm1/localconnection_properties/test.toml create mode 100644 tests/tests/swfs/avm2/localconnection/CustomLocalConnection.as create mode 100644 tests/tests/swfs/avm2/localconnection/Test.as create mode 100644 tests/tests/swfs/avm2/localconnection/avm1child/child.fla create mode 100644 tests/tests/swfs/avm2/localconnection/avm1child/child.swf create mode 100644 tests/tests/swfs/avm2/localconnection/avm2child/Child.as create mode 100644 tests/tests/swfs/avm2/localconnection/avm2child/child.fla create mode 100644 tests/tests/swfs/avm2/localconnection/avm2child/child.swf create mode 100644 tests/tests/swfs/avm2/localconnection/output.txt create mode 100644 tests/tests/swfs/avm2/localconnection/test.fla create mode 100644 tests/tests/swfs/avm2/localconnection/test.swf create mode 100644 tests/tests/swfs/avm2/localconnection/test.toml diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index 3497a24be..8015ca41d 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -36,7 +36,7 @@ pub(crate) mod glow_filter; pub(crate) mod gradient_filter; mod key; mod load_vars; -mod local_connection; +pub(crate) mod local_connection; mod math; mod matrix; pub(crate) mod mouse; diff --git a/core/src/avm1/globals/local_connection.rs b/core/src/avm1/globals/local_connection.rs index 98a58a166..f2b0e2b90 100644 --- a/core/src/avm1/globals/local_connection.rs +++ b/core/src/avm1/globals/local_connection.rs @@ -2,14 +2,139 @@ use crate::avm1::activation::Activation; use crate::avm1::error::Error; +use crate::avm1::globals::shared_object::{deserialize_value, serialize}; +use crate::avm1::object::TObject; use crate::avm1::property_decl::{define_properties_on, Declaration}; -use crate::avm1::{Object, ScriptObject, Value}; -use crate::context::GcContext; +use crate::avm1::{ + ActivationIdentifier, ExecutionReason, NativeObject, Object, ScriptObject, Value, +}; +use crate::context::{GcContext, UpdateContext}; use crate::display_object::TDisplayObject; +use crate::local_connection::{LocalConnectionHandle, LocalConnections}; use crate::string::AvmString; +use flash_lso::types::Value as AmfValue; +use gc_arena::{Collect, Gc}; +use std::cell::RefCell; + +#[derive(Debug, Collect)] +#[collect(require_static)] +struct LocalConnectionData { + handle: RefCell>, +} + +#[derive(Copy, Clone, Debug, Collect)] +#[collect(no_drop)] +pub struct LocalConnection<'gc>(Gc<'gc, LocalConnectionData>); + +impl<'gc> LocalConnection<'gc> { + pub fn cast(value: Value<'gc>) -> Option { + if let Value::Object(object) = value { + if let NativeObject::LocalConnection(local_connection) = object.native() { + return Some(local_connection); + } + } + None + } + + pub fn is_connected(&self) -> bool { + self.0.handle.borrow().is_some() + } + + pub fn connect( + &self, + activation: &mut Activation<'_, 'gc>, + name: AvmString<'gc>, + this: Object<'gc>, + ) -> bool { + if self.is_connected() { + return false; + } + + let connection_handle = activation.context.local_connections.connect( + &LocalConnections::get_domain(activation.context.swf.url()), + this, + &name, + ); + let result = connection_handle.is_some(); + *self.0.handle.borrow_mut() = connection_handle; + result + } + + pub fn disconnect(&self, activation: &mut Activation<'_, 'gc>) { + if let Some(conn_handle) = self.0.handle.take() { + activation.context.local_connections.close(conn_handle); + } + } + + pub fn send_status( + context: &mut UpdateContext<'_, 'gc>, + this: Object<'gc>, + status: &'static str, + ) -> Result<(), Error<'gc>> { + let Some(root_clip) = context.stage.root_clip() else { + tracing::warn!("Ignored LocalConnection callback as there's no root movie"); + return Ok(()); + }; + let mut activation = Activation::from_nothing( + context.reborrow(), + ActivationIdentifier::root("[LocalConnection onStatus]"), + root_clip, + ); + let constructor = activation.context.avm1.prototypes().object_constructor; + let event = constructor + .construct(&mut activation, &[])? + .coerce_to_object(&mut activation); + event.set("level", status.into(), &mut activation)?; + this.call_method( + "onStatus".into(), + &[event.into()], + &mut activation, + ExecutionReason::Special, + )?; + Ok(()) + } + + pub fn run_method( + context: &mut UpdateContext<'_, 'gc>, + this: Object<'gc>, + method_name: AvmString<'gc>, + amf_arguments: Vec, + ) -> Result<(), Error<'gc>> { + let Some(root_clip) = context.stage.root_clip() else { + tracing::warn!("Ignored LocalConnection callback as there's no root movie"); + return Ok(()); + }; + let mut activation = Activation::from_nothing( + context.reborrow(), + ActivationIdentifier::root("[LocalConnection call]"), + root_clip, + ); + let mut args = Vec::with_capacity(amf_arguments.len()); + for arg in amf_arguments { + let reader = flash_lso::read::Reader::default(); + let value = deserialize_value( + &mut activation, + &arg, + &reader.amf0_decoder, + &mut Default::default(), + ); + args.push(value); + } + this.call_method( + method_name, + &args, + &mut activation, + ExecutionReason::Special, + )?; + Ok(()) + } +} const PROTO_DECLS: &[Declaration] = declare_properties! { - "domain" => method(domain; DONT_DELETE | READ_ONLY); + "domain" => method(domain; DONT_DELETE | DONT_ENUM); + "connect" => method(connect; DONT_DELETE | DONT_ENUM); + "close" => method(close; DONT_DELETE | DONT_ENUM); + "send" => method(send; DONT_DELETE | DONT_ENUM); }; pub fn domain<'gc>( @@ -18,29 +143,104 @@ pub fn domain<'gc>( _args: &[Value<'gc>], ) -> Result, Error<'gc>> { let movie = activation.base_clip().movie(); + let domain = LocalConnections::get_domain(movie.url()); - let domain = if let Ok(url) = url::Url::parse(movie.url()) { - if url.scheme() == "file" { - "localhost".into() - } else if let Some(domain) = url.domain() { - AvmString::new_utf8(activation.context.gc_context, domain) - } else { - // no domain? - "localhost".into() - } - } else { - tracing::error!("LocalConnection::domain: Unable to parse movie URL"); - return Ok(Value::Null); - }; - - Ok(Value::String(domain)) + Ok(Value::String(AvmString::new_utf8( + activation.context.gc_context, + domain, + ))) } -pub fn constructor<'gc>( - _activation: &mut Activation<'_, 'gc>, +pub fn connect<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let Some(Value::String(connection_name)) = args.get(0) else { + // This is deliberately not a coercion, Flash tests the type + return Ok(false.into()); + }; + if connection_name.is_empty() || connection_name.contains(b':') { + return Ok(false.into()); + } + + if let Some(local_connection) = LocalConnection::cast(this.into()) { + return Ok(local_connection + .connect(activation, *connection_name, this) + .into()); + } + + Ok(Value::Undefined) +} + +pub fn send<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let Some(Value::String(connection_name)) = args.get(0) else { + // This is deliberately not a coercion, Flash tests the type + return Ok(false.into()); + }; + let Some(Value::String(method_name)) = args.get(1) else { + // This is deliberately not a coercion, Flash tests the type + return Ok(false.into()); + }; + + if connection_name.is_empty() || method_name.is_empty() { + return Ok(false.into()); + } + + if method_name == b"send" + || method_name == b"connect" + || method_name == b"close" + || method_name == b"allowDomain" + || method_name == b"allowInsecureDomain" + || method_name == b"domain" + { + return Ok(false.into()); + } + + let mut amf_arguments = Vec::with_capacity(args.len() - 2); + for arg in &args[2..] { + amf_arguments.push(serialize(activation, *arg)); + } + + activation.context.local_connections.send( + &LocalConnections::get_domain(activation.context.swf.url()), + this, + *connection_name, + *method_name, + amf_arguments, + ); + Ok(true.into()) +} + +pub fn close<'gc>( + activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { + if let Some(local_connection) = LocalConnection::cast(this.into()) { + local_connection.disconnect(activation); + } + Ok(Value::Undefined) +} + +pub fn constructor<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + this.set_native( + activation.gc(), + NativeObject::LocalConnection(LocalConnection(Gc::new( + activation.gc(), + LocalConnectionData { + handle: RefCell::new(None), + }, + ))), + ); Ok(this.into()) } diff --git a/core/src/avm1/object.rs b/core/src/avm1/object.rs index e42e9b197..e4f8bbc3b 100644 --- a/core/src/avm1/object.rs +++ b/core/src/avm1/object.rs @@ -12,6 +12,7 @@ use crate::avm1::globals::drop_shadow_filter::DropShadowFilter; use crate::avm1::globals::file_reference::FileReferenceObject; use crate::avm1::globals::glow_filter::GlowFilter; use crate::avm1::globals::gradient_filter::GradientFilter; +use crate::avm1::globals::local_connection::LocalConnection; use crate::avm1::globals::netconnection::NetConnection; use crate::avm1::globals::shared_object::SharedObject; use crate::avm1::globals::transform::TransformObject; @@ -66,6 +67,7 @@ pub enum NativeObject<'gc> { XmlSocket(XmlSocket<'gc>), FileReference(FileReferenceObject<'gc>), NetConnection(NetConnection<'gc>), + LocalConnection(LocalConnection<'gc>), } /// Represents an object that can be directly interacted with by the AVM diff --git a/core/src/avm2/amf.rs b/core/src/avm2/amf.rs index 77e800a3b..2539a76e8 100644 --- a/core/src/avm2/amf.rs +++ b/core/src/avm2/amf.rs @@ -55,22 +55,27 @@ pub fn serialize_value<'gc>( // Don't serialize properties from the vtable (we don't want a 'length' field) recursive_serialize(activation, o, &mut values, None, amf_version, object_table) .unwrap(); - - let mut dense = vec![]; - let mut sparse = vec![]; - // ActionScript `Array`s can have non-number properties, and these properties - // are confirmed and tested to also be serialized, so do not limit the values - // iterated over by the length of the internal array data. - for (i, elem) in values.into_iter().enumerate() { - if elem.name == i.to_string() { - dense.push(elem.value.clone()); - } else { - sparse.push(elem); - } - } - let len = o.as_array_storage().unwrap().length() as u32; - Some(AmfValue::ECMAArray(dense, sparse, len)) + + if amf_version == AMFVersion::AMF3 { + let mut dense = vec![]; + let mut sparse = vec![]; + // ActionScript `Array`s can have non-number properties, and these properties + // are confirmed and tested to also be serialized, so do not limit the values + // iterated over by the length of the internal array data. + for (i, elem) in values.into_iter().enumerate() { + if elem.name == i.to_string() { + dense.push(elem.value.clone()); + } else { + sparse.push(elem); + } + } + + Some(AmfValue::ECMAArray(dense, sparse, len)) + } else { + // TODO: is this right? + Some(AmfValue::ECMAArray(vec![], values, len)) + } } else if let Some(vec) = o.as_vector_storage() { let val_type = vec.value_type(); if val_type == Some(activation.avm2().classes().int.inner_class_definition()) { diff --git a/core/src/avm2/error.rs b/core/src/avm2/error.rs index 502d49fcd..57233a671 100644 --- a/core/src/avm2/error.rs +++ b/core/src/avm2/error.rs @@ -524,6 +524,23 @@ pub fn make_error_2037<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc> } } +#[inline(never)] +#[cold] +pub fn make_error_2085<'gc>(activation: &mut Activation<'_, 'gc>, param_name: &str) -> Error<'gc> { + let err = argument_error( + activation, + &format!( + "Error #2085: Parameter {} must be non-empty string.", + param_name + ), + 2007, + ); + match err { + Ok(err) => Error::AvmError(err), + Err(err) => err, + } +} + #[inline(never)] #[cold] pub fn make_error_2097<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc> { diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index e2b83236b..d27c6ada4 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -165,6 +165,7 @@ pub struct SystemClasses<'gc> { pub netstatusevent: ClassObject<'gc>, pub shaderfilter: ClassObject<'gc>, pub statusevent: ClassObject<'gc>, + pub asyncerrorevent: ClassObject<'gc>, pub contextmenuevent: ClassObject<'gc>, pub filereference: ClassObject<'gc>, pub filefilter: ClassObject<'gc>, @@ -293,6 +294,7 @@ impl<'gc> SystemClasses<'gc> { netstatusevent: object, shaderfilter: object, statusevent: object, + asyncerrorevent: object, contextmenuevent: object, filereference: object, filefilter: object, @@ -804,6 +806,7 @@ fn load_playerglobal<'gc>( ("flash.events", "UncaughtErrorEvents", uncaughterrorevents), ("flash.events", "NetStatusEvent", netstatusevent), ("flash.events", "StatusEvent", statusevent), + ("flash.events", "AsyncErrorEvent", asyncerrorevent), ("flash.events", "ContextMenuEvent", contextmenuevent), ("flash.events", "FocusEvent", focusevent), ("flash.geom", "Matrix", matrix), diff --git a/core/src/avm2/globals/flash/net/LocalConnection.as b/core/src/avm2/globals/flash/net/LocalConnection.as index e41fe837b..39a70d581 100644 --- a/core/src/avm2/globals/flash/net/LocalConnection.as +++ b/core/src/avm2/globals/flash/net/LocalConnection.as @@ -25,21 +25,7 @@ package flash.net { public native function connect(connectionName:String):void; - public function send(connectionName: String, methodName: String, ... arguments):void { - if (connectionName === null) { - throw new TypeError("Error #2007: Parameter connectionName must be non-null.", 2007); - } - if (methodName === null) { - throw new TypeError("Error #2007: Parameter methodName must be non-null.", 2007); - } - - var self = this; - setTimeout(function() { - self.send_internal(connectionName, methodName, arguments); - }, 0); - } - - private native function send_internal(connectionName: String, methodName: String, args: Array):void; + public native function send(connectionName: String, methodName: String, ... arguments):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 c92a89bb8..7f055b589 100644 --- a/core/src/avm2/globals/flash/net/local_connection.rs +++ b/core/src/avm2/globals/flash/net/local_connection.rs @@ -1,12 +1,13 @@ -use crate::avm2::error::{argument_error, make_error_2007}; +use crate::avm2::amf::serialize_value; +use crate::avm2::error::{argument_error, make_error_2004, make_error_2085, Error2004Type}; use crate::avm2::object::TObject; use crate::avm2::parameters::ParametersExt; -use crate::avm2::{Activation, Avm2, Error, Object, Value}; +use crate::avm2::{Activation, Error, Object, Value}; use crate::string::AvmString; - -use crate::avm2_stub_method; +use flash_lso::types::{AMFVersion, Value as AmfValue}; pub use crate::avm2::object::local_connection_allocator; +use crate::local_connection::LocalConnections; /// Implements `domain` getter pub fn get_domain<'gc>( @@ -15,60 +16,56 @@ pub fn get_domain<'gc>( _args: &[Value<'gc>], ) -> Result, Error<'gc>> { let movie = &activation.context.swf; + let domain = LocalConnections::get_domain(movie.url()); - let domain = if let Ok(url) = url::Url::parse(movie.url()) { - if url.scheme() == "file" { - "localhost".into() - } else if let Some(domain) = url.domain() { - AvmString::new_utf8(activation.context.gc_context, domain) - } else { - // no domain? - "localhost".into() - } - } else { - tracing::error!("LocalConnection::domain: Unable to parse movie URL"); - return Ok(Value::Null); - }; - - Ok(Value::String(domain)) + Ok(Value::String(AvmString::new_utf8( + activation.context.gc_context, + domain, + ))) } /// Implements `LocalConnection.send` -pub fn send_internal<'gc>( +pub fn send<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - // Already null-checked by the AS wrapper `LocalConnection.send` - let connection_name = args.get_value(0); + let connection_name = args.get_string_non_null(activation, 0, "connectionName")?; + let method_name = args.get_string_non_null(activation, 1, "methodName")?; - let connection_name = connection_name.coerce_to_string(activation)?; - - let event_name = if activation - .context - .local_connections - .all_by_name(connection_name) - .is_empty() + if connection_name.is_empty() { + return Err(make_error_2085(activation, "connectionName")); + } + if method_name.is_empty() { + return Err(make_error_2085(activation, "methodName")); + } + if &method_name == b"send" + || &method_name == b"connect" + || &method_name == b"close" + || &method_name == b"allowDomain" + || &method_name == b"allowInsecureDomain" + || &method_name == b"domain" { - "error" - } else { - avm2_stub_method!(activation, "flash.net.LocalConnection", "send"); + return Err(make_error_2004(activation, Error2004Type::ArgumentError)); + } - "status" - }; + let mut amf_arguments = Vec::with_capacity(args.len() - 2); + for arg in &args[2..] { + amf_arguments.push( + serialize_value(activation, *arg, AMFVersion::AMF0, &mut Default::default()) + .unwrap_or(AmfValue::Undefined), + ); + } - let event = activation.avm2().classes().statusevent.construct( - activation, - &[ - "status".into(), - false.into(), - false.into(), - Value::Null, - event_name.into(), - ], - )?; - - Avm2::dispatch_event(&mut activation.context, event, this); + if let Some(local_connection) = this.as_local_connection_object() { + activation.context.local_connections.send( + &LocalConnections::get_domain(activation.context.swf.url()), + (activation.domain(), local_connection), + connection_name, + method_name, + amf_arguments, + ); + } Ok(Value::Undefined) } @@ -79,22 +76,24 @@ pub fn connect<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let connection_name = args.get_value(0); - if matches!(connection_name, Value::Null) { - return Err(make_error_2007(activation, "connectionName")); - }; + let connection_name = args.get_string_non_null(activation, 0, "connectionName")?; + if connection_name.is_empty() { + return Err(make_error_2085(activation, "connectionName")); + } + if connection_name.contains(b':') { + return Err(make_error_2004(activation, Error2004Type::ArgumentError)); + } if let Some(local_connection) = this.as_local_connection_object() { - if local_connection.is_connected() { + if !local_connection.connect(activation, connection_name) { + // This triggers both if this object is already connected, OR there's something else taking the name + // (The error message is misleading, in that case!) return Err(Error::AvmError(argument_error( activation, "Error #2082: Connect failed because the object is already connected.", 2082, )?)); } - - let connection_name = connection_name.coerce_to_string(activation)?; - local_connection.connect(activation, connection_name); } Ok(Value::Undefined) diff --git a/core/src/avm2/object/local_connection_object.rs b/core/src/avm2/object/local_connection_object.rs index f6a35f8b8..989255d38 100644 --- a/core/src/avm2/object/local_connection_object.rs +++ b/core/src/avm2/object/local_connection_object.rs @@ -1,11 +1,14 @@ use crate::avm2::activation::Activation; +use crate::avm2::amf::deserialize_value; use crate::avm2::object::script_object::ScriptObjectData; use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; use crate::avm2::value::Value; -use crate::avm2::Error; -use crate::local_connection::{LocalConnection, LocalConnectionHandle}; +use crate::avm2::{Avm2, Domain, Error}; +use crate::context::UpdateContext; +use crate::local_connection::{LocalConnectionHandle, LocalConnections}; use crate::string::AvmString; use core::fmt; +use flash_lso::types::Value as AmfValue; use gc_arena::{Collect, GcCell, GcWeakCell, Mutation}; use std::cell::{Ref, RefMut}; @@ -42,7 +45,7 @@ impl fmt::Debug for LocalConnectionObject<'_> { } } -#[derive(Clone, Collect)] +#[derive(Collect)] #[collect(no_drop)] pub struct LocalConnectionObjectData<'gc> { /// Base script object @@ -57,30 +60,91 @@ impl<'gc> LocalConnectionObject<'gc> { self.0.read().connection_handle.is_some() } - pub fn connection_handle(&self) -> Option { - self.0.read().connection_handle - } + pub fn connect(&self, activation: &mut Activation<'_, 'gc>, name: AvmString<'gc>) -> bool { + if self.is_connected() { + return false; + } - pub fn connect(&self, activation: &mut Activation<'_, 'gc>, name: AvmString<'gc>) { - assert!(!self.is_connected()); - - let connection_handle = activation - .context - .local_connections - .insert(LocalConnection::new(*self, name)); + let connection_handle = activation.context.local_connections.connect( + &LocalConnections::get_domain(activation.context.swf.url()), + (activation.domain(), *self), + &name, + ); + let result = connection_handle.is_some(); self.0 .write(activation.context.gc_context) - .connection_handle = Some(connection_handle); + .connection_handle = connection_handle; + result } pub fn disconnect(&self, activation: &mut Activation<'_, 'gc>) { - if let Some(conn_handle) = self.0.read().connection_handle { - activation.context.local_connections.remove(conn_handle); + if let Some(conn_handle) = self + .0 + .write(activation.context.gc_context) + .connection_handle + .take() + { + activation.context.local_connections.close(conn_handle); + } + } + + pub fn send_status(&self, context: &mut UpdateContext<'_, 'gc>, status: &'static str) { + let mut activation = Activation::from_nothing(context.reborrow()); + if let Ok(event) = activation.avm2().classes().statusevent.construct( + &mut activation, + &[ + "status".into(), + false.into(), + false.into(), + Value::Null, + status.into(), + ], + ) { + Avm2::dispatch_event(&mut activation.context, event, (*self).into()); + } + } + + pub fn run_method( + &self, + context: &mut UpdateContext<'_, 'gc>, + domain: Domain<'gc>, + method_name: AvmString<'gc>, + amf_arguments: Vec, + ) { + let mut activation = Activation::from_domain(context.reborrow(), domain); + let mut arguments = Vec::with_capacity(amf_arguments.len()); + + for argument in amf_arguments { + arguments + .push(deserialize_value(&mut activation, &argument).unwrap_or(Value::Undefined)); } - self.0 - .write(activation.context.gc_context) - .connection_handle = None; + 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(&mut activation.context, event, (*self).into()); + } + } + _ => { + tracing::error!("Unhandled error dispatching AVM2 LocalConnection method call to '{method_name}': {e}"); + } + } + } + } } } diff --git a/core/src/local_connection.rs b/core/src/local_connection.rs index 842ec6693..74d33a7d3 100644 --- a/core/src/local_connection.rs +++ b/core/src/local_connection.rs @@ -1,55 +1,130 @@ +use crate::avm1::globals::local_connection::LocalConnection as Avm1LocalConnectionObject; use crate::avm1::Object as Avm1Object; use crate::avm2::object::LocalConnectionObject; +use crate::avm2::Domain as Avm2Domain; +use crate::context::UpdateContext; use crate::string::AvmString; +use flash_lso::types::Value as AmfValue; +use fnv::FnvHashMap; use gc_arena::Collect; -use slotmap::{new_key_type, SlotMap}; +use ruffle_wstr::{WStr, WString}; +use std::borrow::Cow; -new_key_type! { - pub struct LocalConnectionHandle; -} - -#[derive(Collect)] +#[derive(Clone, Collect)] #[collect(no_drop)] pub enum LocalConnectionKind<'gc> { - Avm2(LocalConnectionObject<'gc>), + Avm2(Avm2Domain<'gc>, LocalConnectionObject<'gc>), Avm1(Avm1Object<'gc>), } -impl<'gc> From> for LocalConnectionKind<'gc> { - fn from(obj: LocalConnectionObject<'gc>) -> Self { - Self::Avm2(obj) +impl<'gc> From<(Avm2Domain<'gc>, LocalConnectionObject<'gc>)> for LocalConnectionKind<'gc> { + fn from(obj: (Avm2Domain<'gc>, LocalConnectionObject<'gc>)) -> Self { + Self::Avm2(obj.0, obj.1) } } -#[derive(Collect)] -#[collect(no_drop)] -pub struct LocalConnection<'gc> { - object: LocalConnectionKind<'gc>, - - connection_name: AvmString<'gc>, +impl<'gc> From> for LocalConnectionKind<'gc> { + fn from(obj: Avm1Object<'gc>) -> Self { + Self::Avm1(obj) + } } -impl<'gc> LocalConnection<'gc> { - pub fn new( - object: impl Into>, - connection_name: AvmString<'gc>, - ) -> Self { - Self { - object: object.into(), - connection_name, +impl<'gc> LocalConnectionKind<'gc> { + pub fn send_status(&self, context: &mut UpdateContext<'_, 'gc>, status: &'static str) { + match self { + LocalConnectionKind::Avm2(_domain, object) => { + object.send_status(context, status); + } + LocalConnectionKind::Avm1(object) => { + if let Err(e) = Avm1LocalConnectionObject::send_status(context, *object, status) { + tracing::error!("Unhandled AVM1 error during LocalConnection onStatus: {e}"); + } + } + } + } + + pub fn run_method( + &self, + context: &mut UpdateContext<'_, 'gc>, + method_name: AvmString<'gc>, + arguments: Vec, + ) { + match self { + LocalConnectionKind::Avm2(domain, object) => { + object.run_method(context, *domain, method_name, arguments); + } + LocalConnectionKind::Avm1(object) => { + if let Err(e) = + Avm1LocalConnectionObject::run_method(context, *object, method_name, arguments) + { + tracing::error!("Unhandled AVM1 error during LocalConnection onStatus: {e}"); + } + } } } } -/// Manages the collection of local connections. -pub struct LocalConnections<'gc> { - connections: SlotMap>, +#[derive(Collect)] +#[collect(no_drop)] +pub struct QueuedMessage<'gc> { + source: LocalConnectionKind<'gc>, + kind: QueuedMessageKind<'gc>, } -unsafe impl<'gc> Collect for LocalConnections<'gc> { +#[derive(Collect)] +#[collect(no_drop)] +pub enum QueuedMessageKind<'gc> { + Failure, + Message { + #[collect(require_static)] + connection_name: WString, + method_name: AvmString<'gc>, + #[collect(require_static)] + arguments: Vec, + }, +} + +impl<'gc> QueuedMessageKind<'gc> { + pub fn deliver(self, source: LocalConnectionKind<'gc>, context: &mut UpdateContext<'_, 'gc>) { + match self { + QueuedMessageKind::Failure => { + source.send_status(context, "error"); + } + QueuedMessageKind::Message { + connection_name, + method_name, + arguments, + } => { + if let Some(receiver) = context.local_connections.find_listener(&connection_name) { + source.send_status(context, "status"); + receiver.run_method(context, method_name, arguments); + } else { + source.send_status(context, "error"); + } + } + } + } +} + +/// An opaque handle to an actively listening LocalConnection. +/// Owning this handle represents ownership of a LocalConnection; +/// However, a LocalConnection must be manually closed, you can't just Drop this handle. +#[derive(Debug)] +pub struct LocalConnectionHandle(WString); + +/// Manages the collection of local connections. +pub struct LocalConnections<'gc> { + connections: FnvHashMap>, + messages: Vec>, +} + +unsafe impl Collect for LocalConnections<'_> { fn trace(&self, cc: &gc_arena::Collection) { - for (_, connection) in self.connections.iter() { - connection.trace(cc) + for (_, v) in self.connections.iter() { + v.trace(cc); + } + for m in self.messages.iter() { + m.trace(cc); } } } @@ -57,26 +132,108 @@ unsafe impl<'gc> Collect for LocalConnections<'gc> { impl<'gc> LocalConnections<'gc> { pub fn empty() -> Self { Self { - connections: SlotMap::with_key(), + connections: Default::default(), + messages: Default::default(), } } - pub fn insert(&mut self, connection: LocalConnection<'gc>) -> LocalConnectionHandle { - self.connections.insert(connection) + pub fn connect>>( + &mut self, + domain: &str, + connection: C, + name: &WStr, + ) -> Option { + let key = if name.starts_with(b'_') { + name.to_ascii_lowercase() + } else { + let mut key = WString::from_utf8(Self::get_superdomain(domain)); + key.push_char(':'); + key.push_str(name); + key.make_ascii_lowercase(); + key + }; + + if self.connections.contains_key(&key) { + None + } else { + self.connections.insert(key.to_owned(), connection.into()); + Some(LocalConnectionHandle(key.to_owned())) + } } - pub fn remove(&mut self, handle: LocalConnectionHandle) { - self.connections.remove(handle); + pub fn close(&mut self, handle: LocalConnectionHandle) { + self.connections.remove(&handle.0); } - pub fn all_by_name(&self, requested_name: AvmString<'gc>) -> Vec<&LocalConnection<'gc>> { - let mut conns = Vec::new(); - for (_, connection) in self.connections.iter() { - if connection.connection_name == requested_name { - conns.push(connection); + pub fn send>>( + &mut self, + domain: &str, + source: C, + connection_name: AvmString<'gc>, + method_name: AvmString<'gc>, + arguments: Vec, + ) { + // There's two checks for "is connected": + // 1 - At `send()` time, if there's no connections, just immediately queue up a failure + // 2 - At `update_connections()` time, if the connection couldn't be found, queue up a failure + // Even if one becomes available between send and update, it won't be used + // Similarly, if one becomes unavailable between send and update, it'll error + // If something *else* takes its place between send and update, it'll use that instead + + let mut connection_name = connection_name.to_ascii_lowercase(); + if !connection_name.contains(b':') && !connection_name.starts_with(b'_') { + let mut result = WString::from_utf8(Self::get_superdomain(domain)); + result.push_char(':'); + result.push_str(&connection_name); + connection_name = result; + } + + let kind = if self.find_listener(&connection_name).is_some() { + QueuedMessageKind::Message { + connection_name, + method_name, + arguments, } + } else { + QueuedMessageKind::Failure + }; + self.messages.push(QueuedMessage { + source: source.into(), + kind, + }); + } + + fn find_listener(&self, name: &WStr) -> Option> { + self.connections.get(name).cloned() + } + + pub fn update_connections(context: &mut UpdateContext<'_, 'gc>) { + if context.local_connections.messages.is_empty() { + return; } - conns + for message in std::mem::take(&mut context.local_connections.messages) { + message.kind.deliver(message.source, context); + } + } + + pub fn get_domain(url: &str) -> Cow<'static, str> { + if let Ok(url) = url::Url::parse(url) { + if url.scheme() == "file" { + Cow::Borrowed("localhost") + } else if let Some(domain) = url.domain() { + Cow::Owned(domain.to_owned()) + } else { + // no domain? + Cow::Borrowed("localhost") + } + } else { + tracing::error!("LocalConnection: Unable to parse movie URL: {url}"); + return Cow::Borrowed("unknown"); // this is surely an error but it'll hopefully highlight this case in issues for us + } + } + + pub fn get_superdomain(domain: &str) -> &str { + domain.rsplit_once('.').map(|(_, b)| b).unwrap_or(domain) } } diff --git a/core/src/player.rs b/core/src/player.rs index 37e79a6b0..e67e7c391 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -1715,6 +1715,7 @@ impl Player { run_all_phases_avm2(context); Avm1::run_frame(context); AudioManager::update_sounds(context); + LocalConnections::update_connections(context); // Only run the current list of callbacks - any callbacks added during callback execution // will be run at the end of the *next* frame. diff --git a/tests/tests/swfs/avm1/localconnection/CustomLocalConnection.as b/tests/tests/swfs/avm1/localconnection/CustomLocalConnection.as new file mode 100644 index 000000000..56970b1d4 --- /dev/null +++ b/tests/tests/swfs/avm1/localconnection/CustomLocalConnection.as @@ -0,0 +1,15 @@ +class CustomLocalConnection extends LocalConnection { + function test() { + trace("custom.test was called with " + arguments.length + " argument" + (arguments.length == 0 ? "" : "s")); + if (arguments.length > 0) { + trace(" " + repr(arguments)); + } + } + + function throwAnError() { + trace("custom.throwAnError was called"); + //throw "aah!"; // [NA] this crashes every Flash Player I've tried + //throw {}; // [NA] this causes an error when constructing the AsyncErrorEvent + //throw new Error("aaah!"); + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm1/localconnection/avm1child/child.fla b/tests/tests/swfs/avm1/localconnection/avm1child/child.fla new file mode 100644 index 0000000000000000000000000000000000000000..af61bd7a8ce88d99c4be7e9609e0d3a946c2c3af GIT binary patch literal 5487 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 tZ>0Pg00ay0lcK+!^6%vO3G0Eu{${G0A`i3! zG)YO*4Y<=$q-$GLmOPRiC&Mr-flYRJhGCdt!*5{KCBJ|T|HhnqC4Z%+gpwI&bdJt> z?|YtmuZ|(|9N_Y=0M|JvU%dhV-_Bi$0Ia)4J-=VB$j2?q_VO4NQcd4)=M|;f?Pj`5 z8OLoX>e||xlFcc(TpBCV-ihsN$7$QUlPbzgp-ep8HQT=F*fNc^L#N{xQmIh(xYZuY z+FnL8oI{e)otAQ}wG}m!Rj3XY(q(op}c0vn@?8c zw0zhxEkj;jRo9iXonu<0jK5+on_Et2SJJ9_pf2Z^R`Y81qiimp&5rd3l|kzc@ijy9 zwX@B+rM$Y5U(Ss;Pt=C(j$zhM&ec4sl-HFrB+tjz(1&Q-9oJ&}W#|gANQ>CMhj>+n z*Uj{c&n9g%dMqFz@PyJC)?S1{f4(%%Anfw_euTr z)!xBLn<)Flb2_e0u>4MVV0L!$+qUOxwobOoMNDK&)5x#oRyWE^s~_j^VXLC58@ZK@ z(*4!t<;wl#jg@l1ezJ9-R(A9b=adXrIf8~EmgKEx%;nE zogy*xN?h}Z7!}uPv0Z3uu16@TLTZ4DVq$#r<`hiozYX?E>p;zTlYw4o9jH0~2h%@F z$is~pahiUlmGC22lwp(a_xqm+FQWk8-{3w3i`Wgn39<~94tA$?NL=uI*R&ge5?Y%) z34#Xk_YS`zy1#9}BD6bR6H2bDoq+u1GyKhfOWH2Xz%%e1*9X~Q#q!ti4afxrkTti#@PG&4H(jS&vbS8$xCnwxh+z@n zo7xE;82tr&4!t-a$>8rh4!C453L+=+66`*l{e?$VJrP7+b}@kQFh3H3*ql7K02Jd5HGG(@<`5LlSNkPcC%Dgs;bVldoi zMI1@vM{h;;c8(enxfq9bq)34lt@FJLXc&_`O-2joGR7>#aEMv9D6T2R#8|aR7Z;Ij zP;+4*g*j&j$sZtj|Oyn+9ae*ikj7uC1BFQs5&9j~6 z*$Ql@SD|+i#Hbj-St9fZ?Dk?16X;AA3rsap4P_Au|Cnju%D?yNGEp(kYA!K1_qenB zCIq0L;v>*=vW+X=VyLMZiLsCZ76N5~B7gRKiIE7VFEd2LA+L;v6sBRi#0W;EtIX1m zVU##O8exFuQ|L(n8UeAe#b>L%83;+yfpjZ4Kp`}nawcs&M>2fnVZARtuQk`%p`)0Ch*)5@ZN*s9z4LOzX3xR&Kh~_t+4 0) { + trace(" " + repr(arguments)); + } + } + } + + private function getObjectId(needle: Object, haystack: Array): String { + for (var i = 0; i < haystack.length; i++) { + if (haystack[i] === needle) { + return i; + } + } + return null; + } + + public function repr(value: *, indent: String = " ", seenObjects: Array = null) { + if (seenObjects == null) { + seenObjects = []; + } + if (value === lc) { + return "lc"; + } + + if (value === undefined || value === null || value === true || value === false || value is Number) { + return String(value); + } else if (value is String) { + return escapeString(value); + } else { + var existingId = getObjectId(value, seenObjects); + if (existingId != null) { + return "*" + existingId; + } + existingId = seenObjects.length; + seenObjects.push(value); + if (value is Array) { + if (value.length == 0) { + return "*" + existingId + " []"; + } else { + var result = "*" + existingId + " [\n"; + var nextIndent = indent + " "; + for (var i = 0; i < value.length; i++) { + result += nextIndent + repr(value[i], nextIndent, seenObjects) + "\n"; + } + return result + indent + "]"; + } + } else { + var keys = []; + for (var key in value) { + keys.push(key); + } + keys.sort(); + + var result = "*" + existingId + " " + getQualifiedClassName(value) + " {"; + + if (keys.length == 0) { + return result + "}"; + } else { + result += "\n"; + var nextIndent = indent + " "; + for (var i = 0; i < keys.length; i++) { + result += nextIndent + keys[i] + " = " + repr(value[keys[i]], nextIndent, seenObjects) + "\n"; + } + return result + indent + "}"; + } + } + } + } + + public function escapeString(input: String): String { + var output:String = "\""; + for (var i:int = 0; i < input.length; i++) { + var char:String = input.charAt(i); + switch (char) { + case "\\": + output += "\\\\"; + break; + case "\"": + output += "\\\""; + break; + case "\n": + output += "\\n"; + break; + case "\r": + output += "\\r"; + break; + case "\t": + output += "\\t"; + break; + default: + output += char; + } + } + return output + "\""; + } + } +} diff --git a/tests/tests/swfs/avm1/localconnection/avm2child/child.fla b/tests/tests/swfs/avm1/localconnection/avm2child/child.fla new file mode 100644 index 0000000000000000000000000000000000000000..39cd99bd3d5eec53f2df2ebf847e361f846bb030 GIT binary patch literal 4574 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 F{{VBhe#Za+ literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm1/localconnection/avm2child/child.swf b/tests/tests/swfs/avm1/localconnection/avm2child/child.swf new file mode 100644 index 0000000000000000000000000000000000000000..30d9aaced1cb5ba585797d9352aa8fe874633d24 GIT binary patch literal 1737 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

E*t*$@c0SRV@>dyA06LyLIRbDP`)hDp!=}M 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 K?L7W7fd2t)GbWAz literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm1/localconnection_properties/test.swf b/tests/tests/swfs/avm1/localconnection_properties/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..ad4cbdebb63558aeb7f0dbc8677eaaedcd570146 GIT binary patch literal 911 zcmV;A191F9S5psB2mkQb zu%=}MBExUJkPI>&f1NI)NWq}o3LHOln1^IcE?6%-Os7Y(gRVc3^>~)p?1E-3*2M$j z<6Kt7Qb&OFN2&s#eITdzqZp*7EwBqWT&DX|~U+=2-(=>RpCcfPO}C(x{$R z+f`_`zpu8SRXsA!o95|Hpd&zLvyE!aGt+?KHYlJZl%%qxEGsLk;=cfnOT#26k9LP^ zpsYTE(Weq+m6SXmn`gf!0EP)jp8S!7uVJ_h%9CY?AgdANi9A;l&EJ#o_nl0v?aK3u z=!Dc9|F4APuS>qv5oHNlPnl7?F=-m>no*aH)Q(`DCuWC=>-r1H+Pu#a*)3&lZboxQ zswfcGwqGq&yO%R1=@4YF6%}^O;bWLMbt%i6Bh47_wEk)~{Ib5FlRTd_7j%mXCp(|p l^!fXmZls#i;6**|P~R1sTZd`kWt6G^3*U)u{{bkdKkXKR%trtK literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm1/localconnection_properties/test.toml b/tests/tests/swfs/avm1/localconnection_properties/test.toml new file mode 100644 index 000000000..dbee897f5 --- /dev/null +++ b/tests/tests/swfs/avm1/localconnection_properties/test.toml @@ -0,0 +1 @@ +num_frames = 1 diff --git a/tests/tests/swfs/avm2/localconnection/CustomLocalConnection.as b/tests/tests/swfs/avm2/localconnection/CustomLocalConnection.as new file mode 100644 index 000000000..4c2d4d3f2 --- /dev/null +++ b/tests/tests/swfs/avm2/localconnection/CustomLocalConnection.as @@ -0,0 +1,26 @@ +package { + import flash.net.LocalConnection; + + public class CustomLocalConnection extends LocalConnection { + private var main: Test; + + public function CustomLocalConnection(main: Test) { + super(); + this.main = main; + } + + public function test() { + trace("custom.test was called with " + arguments.length + " argument" + (arguments.length == 0 ? "" : "s")); + if (arguments.length > 0) { + trace(" " + main.repr(arguments)); + } + } + + public function throwAnError() { + trace("custom.throwAnError was called"); + //throw "aah!"; // [NA] this crashes every Flash Player I've tried + //throw {}; // [NA] this causes an error when constructing the AsyncErrorEvent + //throw new Error("aaah!"); + } + } +} diff --git a/tests/tests/swfs/avm2/localconnection/Test.as b/tests/tests/swfs/avm2/localconnection/Test.as new file mode 100644 index 000000000..11ac9627c --- /dev/null +++ b/tests/tests/swfs/avm2/localconnection/Test.as @@ -0,0 +1,449 @@ +package { + + import flash.display.MovieClip; + import flash.net.LocalConnection; + import flash.utils.getQualifiedClassName; + import flash.events.AsyncErrorEvent; + import flash.events.Event; + import flash.events.SecurityErrorEvent; + import flash.events.StatusEvent; + import flash.system.fscommand; + import flash.net.URLRequest; + import flash.display.Loader; + + + public class Test extends MovieClip { + // Just a safe value, to make things easier... + // Sometimes Flash isn't super consistant about things being done on the same frame + const TICKS_PER_TEST: uint = 3; + + var receiver: LocalConnection = new LocalConnection(); + var sender: LocalConnection = new LocalConnection(); + var custom: LocalConnection; + var recvObject: Object = {}; + var tests: Array = []; + var currentTest = null; + var frameNum: uint = 0; + var totalFrameNum: uint = 0; + var funkyNames = [null, 0, "", " ??? "]; + var protectedFunctions = ["send", "connect", "close", "allowDomain", "allowInsecureDomain", "domain"]; + + public function Test() { + custom = new CustomLocalConnection(this); + + loadMovie("avm2child/child.swf"); + loadMovie("avm1child/child.swf"); + + trace("LocalConnection.isSupported: " + repr(LocalConnection.isSupported)); + trace(""); + + recvObject.test = createTestFunction("recvObject.test"); + + setupEvents(receiver); + setupEvents(sender); + setupEvents(custom); + + addTest("A message to nowhere!", function() { + send(sender, "nowhere", "test"); + }); + + addTest("Both receivers try to connect to the same channel", function() { + connect(receiver, "channel"); + connect(custom, "channel"); + }); + + addTest("A message to an unimplemented function", function() { + send(sender, "channel", "unimplemented"); + }); + + addTest("Receiver tries to connect elsewhere, but can't", function() { + connect(receiver, "elsewhere"); + }); + + addTest("Receiver actually connects elsewhere, and custom is allowed to connect to channel", function() { + close(receiver); + connect(receiver, "elsewhere"); + connect(custom, "channel"); + }); + + addTest("Sender calls test() on 'channel'", function() { + send(sender, "channel", "test"); + }); + + addTest("Client is used", function() { + receiver.client = recvObject; + send(sender, "elsewhere", "test"); + }); + + addTest("Sender calls test() on 'channel'... after the listener is gone", function() { + close(custom); + send(sender, "channel", "test"); + }); + + addTest("Sender calls test() on 'elsewhere'... immediately before the listener is gone", function() { + send(sender, "elsewhere", "test"); + close(receiver); + }); + + addTest("Sender calls test() on 'channel'... before the listener connects", function() { + send(sender, "channel", "test"); + connect(custom, "channel"); + }); + + addTest("Sending to a channel that gets reassigned before end-of-frame", function() { + send(sender, "channel", "test"); + close(custom); + connect(receiver, "channel"); + }); + + addTest("Channels reconnect and receive", function() { + close(custom); + close(receiver); + connect(receiver, "elsewhere"); + send(sender, "channel", "test"); + send(sender, "elsewhere", "test"); + connect(custom, "channel"); + }); + + addTest("A connected listener can also send", function() { + send(receiver, "channel", "test"); + send(receiver, "elsewhere", "test"); + }); + + addTest("A listener throws an error", function() { + // Fun fact: you can crash Flash Player if the thing thrown isn't an object + send(sender, "channel", "throwAnError"); + }); + + addTest("Close something's that's already closed", function() { + sender.close(); + }); + + addTest("Send to funky channel names", function() { + for (var i = 0; i < funkyNames.length; i++) { + send(sender, funkyNames[i], "test"); + } + }); + + addTest("Send to funky methods", function() { + for (var i = 0; i < funkyNames.length; i++) { + send(sender, "channel", funkyNames[i]); + } + }); + + addTest("Connect to funky names", function() { + for (var i = 0; i < funkyNames.length; i++) { + connect(sender, funkyNames[i]); + close(sender); + } + }); + + addTest("Connect to something with a prefix", function() { + connect(sender, "localhost:something"); + close(sender); + }); + + addTest("Send to protected methods", function() { + for (var i = 0; i < protectedFunctions.length; i++) { + send(sender, "channel", protectedFunctions[i]); + } + }); + + addTest("Arguments are sent", function() { + send(sender, "elsewhere", "test", 1, "two", {value: 3}, [4, 5]); + }); + + addTest("Explicit host prefix", function() { + send(sender, "localhost:channel", "test"); + send(sender, "notlocalhost:elsewhere", "test"); + }); + + addTest("Underscores in names", function() { + close(custom); + connect(custom, "_channel"); + send(sender, "_channel", "test"); + }); + + addTest("Underscores in name doesn't allow a prefix", function() { + send(sender, "localhost:channel", "test"); + send(sender, "localhost:_channel", "test"); + }); + + addTest("Case sensitivity", function() { + send(sender, "ELSEWhere", "test"); + send(sender, "LOCalHOST:ElseWhere", "test"); + }); + + addTest("Calling an AVM2 movie", function() { + send(sender, "avm2_child", "test"); + }); + + addTest("Calling an AVM1 movie", function() { + send(sender, "avm1_child", "test"); + }); + + addTest("Argument translations: primitives", function() { + sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", 1, 1.2, true, false, "string", null, undefined); + }); + + addTest("Argument translations: simple array", function() { + sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", [1,2, "three", 4.5, NaN, Infinity]); + }); + + addTest("Argument translations: simple object", function() { + sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", {"nested": {"numbers": [1,2], "string": "hello"}}); + }); + + // [NA] broken in ruffle at time of writing + //addTest("Argument translations: self referential object", function() { + // var obj = {}; + // obj.self = obj; + // obj.nested = {root: obj}; + // sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", obj); + //}); + + //addTest("Argument translations: self referential array", function() { + // var array = []; + // array.push(array); + // sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", array); + //}); + + //addTest("Argument translations: vector", function() { + //var vector = new Vector.(); + //vector.push("hello"); + //vector.push("world"); + //sendToMany(sender, ["avm1_child", "avm2_child", "_channel"], "test", vector); + //}); + + addTest("AVM1 movie throws an error", function() { + send(sender, "avm1_child", "throwAnError"); + }); + + addEventListener(Event.ENTER_FRAME, onEnterFrame); + } + + function loadMovie(path: String) { + var loader = new Loader(); + loader.load(new URLRequest(path)); + addChild(loader); + } + + function onEnterFrame(event: Event) { + totalFrameNum++; + + if (frameNum == TICKS_PER_TEST) { + trace(""); + trace("-- end test: " + currentTest[0] + " --"); + trace(""); + frameNum = 0; + return; // Allow any end-of-frame cleanup before next test + } + if (frameNum == 0) { + currentTest = tests.shift(); + if (currentTest != null) { + trace(""); + trace("-- start test: " + currentTest[0] + " --"); + trace(""); + try { + currentTest[1](); + } catch (e) { + trace("! test stopped with error: " + e); + } + trace(""); + trace("-- end frame: " + currentTest[0] + " --"); + trace(""); + } + } + if (currentTest == null) { + trace("Finished after " + totalFrameNum + " frames"); + fscommand("exit"); + removeEventListener(Event.ENTER_FRAME, onEnterFrame); + return; + } + frameNum++; + } + + function connect(lc: LocalConnection, name: String) { + var doing = repr(lc) + ".connect(" + repr(name) + ")"; + try { + lc.connect(name); + trace(doing); + } catch (e) { + trace(doing + ": ! " + e); + } + } + + function send(lc: LocalConnection, connectionName: String, methodName: String, ...args) { + var doing = repr(lc) + ".send(" + repr(connectionName) + ", " + repr(methodName) + ", " + repr(args) + ")"; + try { + args.unshift(methodName); + args.unshift(connectionName); + lc.send.apply(lc, args); + trace(doing); + } catch (e) { + trace(doing + ": ! " + e); + } + } + + function sendToMany(lc: LocalConnection, connectionNames: Array, methodName: String, ...args) { + args.unshift(methodName); + args.unshift(""); + args.unshift(lc); + for (var i = 0; i < connectionNames.length; i++) { + args[1] = connectionNames[i]; + send.apply(null, args); + } + } + + function close(lc: LocalConnection) { + var doing = repr(lc) + ".close()"; + try { + lc.close(); + trace(doing); + } catch (e) { + trace(doing + ": ! " + e); + } + } + + function addTest(name: String, fn: Function) { + tests.push([name, fn]); + } + + function createTestFunction(name: String) { + return function() { + trace(name + " was called with " + arguments.length + " argument" + (arguments.length == 0 ? "" : "s")); + if (arguments.length > 0) { + trace(" " + repr(arguments)); + } + } + } + + function setupEvents(lc: LocalConnection) { + var name: String = repr(lc); + lc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, function(event: AsyncErrorEvent) { + trace(name + " received event AsyncErrorEvent.ASYNC_ERROR"); + trace(" bubbles: " + repr(event.bubbles)); + trace(" cancelable: " + repr(event.cancelable)); + trace(" error: " + event.error); + trace(" currentTarget: " + repr(event.currentTarget)); + trace(" target: " + repr(event.target)); + trace(""); + }); + lc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(event: SecurityErrorEvent) { + trace(name + " received event SecurityErrorEvent.SECURITY_ERROR"); + trace(" bubbles: " + repr(event.bubbles)); + trace(" cancelable: " + repr(event.cancelable)); + trace(" text: " + repr(event.text)); + trace(" currentTarget: " + repr(event.currentTarget)); + trace(" target: " + repr(event.target)); + trace(""); + }); + lc.addEventListener(StatusEvent.STATUS, function(event: StatusEvent) { + trace(name + " received event StatusEvent.STATUS"); + trace(" bubbles: " + repr(event.bubbles)); + trace(" cancelable: " + repr(event.cancelable)); + trace(" code: " + repr(event.code)); + trace(" currentTarget: " + repr(event.currentTarget)); + trace(" level: " + repr(event.level)); + trace(" target: " + repr(event.target)); + trace(""); + }); + } + + private function getObjectId(needle: Object, haystack: Array): String { + for (var i = 0; i < haystack.length; i++) { + if (haystack[i] === needle) { + return i; + } + } + return null; + } + + public function repr(value: *, indent: String = " ", seenObjects: Array = null) { + if (seenObjects == null) { + seenObjects = []; + } + if (value === receiver) { + return "receiver"; + } else if (value === sender) { + return "sender"; + } else if (value === recvObject) { + return "recvObject"; + } else if (value === custom) { + return "custom"; + } + + if (value === undefined || value === null || value === true || value === false || value is Number) { + return String(value); + } else if (value is String) { + return escapeString(value); + } else { + var existingId = getObjectId(value, seenObjects); + if (existingId != null) { + return "*" + existingId; + } + existingId = seenObjects.length; + seenObjects.push(value); + if (value is Array) { + if (value.length == 0) { + return "*" + existingId + " []"; + } else { + var result = "*" + existingId + " [\n"; + var nextIndent = indent + " "; + for (var i = 0; i < value.length; i++) { + result += nextIndent + repr(value[i], nextIndent, seenObjects) + "\n"; + } + return result + indent + "]"; + } + } else { + var keys = []; + for (var key in value) { + keys.push(key); + } + keys.sort(); + + var result = "*" + existingId + " " + getQualifiedClassName(value) + " {"; + + if (keys.length == 0) { + return result + "}"; + } else { + result += "\n"; + var nextIndent = indent + " "; + for (var i = 0; i < keys.length; i++) { + result += nextIndent + keys[i] + " = " + repr(value[keys[i]], nextIndent, seenObjects) + "\n"; + } + return result + indent + "}"; + } + } + } + } + + public function escapeString(input: String): String { + var output:String = "\""; + for (var i:int = 0; i < input.length; i++) { + var char:String = input.charAt(i); + switch (char) { + case "\\": + output += "\\\\"; + break; + case "\"": + output += "\\\""; + break; + case "\n": + output += "\\n"; + break; + case "\r": + output += "\\r"; + break; + case "\t": + output += "\\t"; + break; + default: + output += char; + } + } + return output + "\""; + } + } + +} diff --git a/tests/tests/swfs/avm2/localconnection/avm1child/child.fla b/tests/tests/swfs/avm2/localconnection/avm1child/child.fla new file mode 100644 index 0000000000000000000000000000000000000000..af61bd7a8ce88d99c4be7e9609e0d3a946c2c3af GIT binary patch literal 5487 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 tZ>0Pg00ay0lcK+!^6%vO3G0Eu{${G0A`i3! zG)YO*4Y<=$q-$GLmOPRiC&Mr-flYRJhGCdt!*5{KCBJ|T|HhnqC4Z%+gpwI&bdJt> z?|YtmuZ|(|9N_Y=0M|JvU%dhV-_Bi$0Ia)4J-=VB$j2?q_VO4NQcd4)=M|;f?Pj`5 z8OLoX>e||xlFcc(TpBCV-ihsN$7$QUlPbzgp-ep8HQT=F*fNc^L#N{xQmIh(xYZuY z+FnL8oI{e)otAQ}wG}m!Rj3XY(q(op}c0vn@?8c zw0zhxEkj;jRo9iXonu<0jK5+on_Et2SJJ9_pf2Z^R`Y81qiimp&5rd3l|kzc@ijy9 zwX@B+rM$Y5U(Ss;Pt=C(j$zhM&ec4sl-HFrB+tjz(1&Q-9oJ&}W#|gANQ>CMhj>+n z*Uj{c&n9g%dMqFz@PyJC)?S1{f4(%%Anfw_euTr z)!xBLn<)Flb2_e0u>4MVV0L!$+qUOxwobOoMNDK&)5x#oRyWE^s~_j^VXLC58@ZK@ z(*4!t<;wl#jg@l1ezJ9-R(A9b=adXrIf8~EmgKEx%;nE zogy*xN?h}Z7!}uPv0Z3uu16@TLTZ4DVq$#r<`hiozYX?E>p;zTlYw4o9jH0~2h%@F z$is~pahiUlmGC22lwp(a_xqm+FQWk8-{3w3i`Wgn39<~94tA$?NL=uI*R&ge5?Y%) z34#Xk_YS`zy1#9}BD6bR6H2bDoq+u1GyKhfOWH2Xz%%e1*9X~Q#q!ti4afxrkTti#@PG&4H(jS&vbS8$xCnwxh+z@n zo7xE;82tr&4!t-a$>8rh4!C453L+=+66`*l{e?$VJrP7+b}@kQFh3H3*ql7K02Jd5HGG(@<`5LlSNkPcC%Dgs;bVldoi zMI1@vM{h;;c8(enxfq9bq)34lt@FJLXc&_`O-2joGR7>#aEMv9D6T2R#8|aR7Z;Ij zP;+4*g*j&j$sZtj|Oyn+9ae*ikj7uC1BFQs5&9j~6 z*$Ql@SD|+i#Hbj-St9fZ?Dk?16X;AA3rsap4P_Au|Cnju%D?yNGEp(kYA!K1_qenB zCIq0L;v>*=vW+X=VyLMZiLsCZ76N5~B7gRKiIE7VFEd2LA+L;v6sBRi#0W;EtIX1m zVU##O8exFuQ|L(n8UeAe#b>L%83;+yfpjZ4Kp`}nawcs&M>2fnVZARtuQk`%p`)0Ch*)5@ZN*s9z4LOzX3xR&Kh~_t+4 0) { + trace(" " + repr(arguments)); + } + } + } + + private function getObjectId(needle: Object, haystack: Array): String { + for (var i = 0; i < haystack.length; i++) { + if (haystack[i] === needle) { + return i; + } + } + return null; + } + + public function repr(value: *, indent: String = " ", seenObjects: Array = null) { + if (seenObjects == null) { + seenObjects = []; + } + if (value === lc) { + return "lc"; + } + + if (value === undefined || value === null || value === true || value === false || value is Number) { + return String(value); + } else if (value is String) { + return escapeString(value); + } else { + var existingId = getObjectId(value, seenObjects); + if (existingId != null) { + return "*" + existingId; + } + existingId = seenObjects.length; + seenObjects.push(value); + if (value is Array) { + if (value.length == 0) { + return "*" + existingId + " []"; + } else { + var result = "*" + existingId + " [\n"; + var nextIndent = indent + " "; + for (var i = 0; i < value.length; i++) { + result += nextIndent + repr(value[i], nextIndent, seenObjects) + "\n"; + } + return result + indent + "]"; + } + } else { + var keys = []; + for (var key in value) { + keys.push(key); + } + keys.sort(); + + var result = "*" + existingId + " " + getQualifiedClassName(value) + " {"; + + if (keys.length == 0) { + return result + "}"; + } else { + result += "\n"; + var nextIndent = indent + " "; + for (var i = 0; i < keys.length; i++) { + result += nextIndent + keys[i] + " = " + repr(value[keys[i]], nextIndent, seenObjects) + "\n"; + } + return result + indent + "}"; + } + } + } + } + + public function escapeString(input: String): String { + var output:String = "\""; + for (var i:int = 0; i < input.length; i++) { + var char:String = input.charAt(i); + switch (char) { + case "\\": + output += "\\\\"; + break; + case "\"": + output += "\\\""; + break; + case "\n": + output += "\\n"; + break; + case "\r": + output += "\\r"; + break; + case "\t": + output += "\\t"; + break; + default: + output += char; + } + } + return output + "\""; + } + } +} diff --git a/tests/tests/swfs/avm2/localconnection/avm2child/child.fla b/tests/tests/swfs/avm2/localconnection/avm2child/child.fla new file mode 100644 index 0000000000000000000000000000000000000000..39cd99bd3d5eec53f2df2ebf847e361f846bb030 GIT binary patch literal 4574 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 F{{VBhe#Za+ literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/localconnection/avm2child/child.swf b/tests/tests/swfs/avm2/localconnection/avm2child/child.swf new file mode 100644 index 0000000000000000000000000000000000000000..30d9aaced1cb5ba585797d9352aa8fe874633d24 GIT binary patch literal 1737 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 literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/localconnection/test.swf b/tests/tests/swfs/avm2/localconnection/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..eeff5ca01f96a2ce6470c7f38b7c0d1f5633f322 GIT binary patch literal 4639 zcmV+)65#DaS5pskBLDz+oXt7ue;dbjGmBm90^k9Hr$~`nQV=OpOB^IcQiwzW0!fjw zDA*)LTecWs0qhEh0W5-Dh+@;!30w7Wo3v`1HfidXy$`3Sdc?ilrcKkkfcUf35B>f6 zB_G<){R7Z9v%3UHNw%7PDSw)MZ{ECn-n<#u_Yrpoq4s@*hA>*}=tBs-)#>FBx>(Mr zk@dx8@qQs+)FUvvG?F!pQY0AM+1Ux~Ob4{`-C+2_g$u#ZRB&o)5;!LH-J&7ipDgO9 zM;1h?VNuc3YVQ|HhiZ#@K+b3>C6Lw%!TWM47!HJjL=P}U z;$=lPwDP8=cjdex+Xl{LTx18(+aSkwhCr#EwWnF4Y`n55Xj{+k@@&sC={E$GCw~RTUri>=i}i}eD+Fg zCLRkf&Cgz;xj!muYg^RP6_PhgTjqbEtx43@mMG`$=AdMsp*21iUY?6D%}>X}i$v>% z#c()2H#2i3J{!7letO#0`V19^zAEM1JtebT)(UhNN^)6ONTx20*r_6EqWO*Fo{^^5 z#|8G3sI4vYodoohsI5)=zohB8D#XK$Y2@kIB^|Vy;DU(aOub%zzuN;15~Gc@QdGn+ zLhpBV4G{wTc~Jm490)n4>EC}}M)c1YtzyJ(D!Sp^Fv_{&-4=S90?Ivd2)QK!Tqdcg z812`!w49G?#iEiXK5(&eS>6?vD@8hOk3ok`FO|z$nF{(glys$>GjSvSn>2R-}4bO?NUmy_A=CU2EFCoD$FHN^P5~ z@s~F4yu7r2XLD&|lda^6hEP`03M8@Y)RkfeK$;qI1Nok5og?~n5BOOImq;Q6wJaBu z896S<#tSig%5yOO(rZX_aAp$mVZKY?E9&wY&%^mW^~) zDf2~b2LQyAl_8*fYo(Ygl+0GGWL!#KSLnJkozDSj*lLQ6ju_jR4o-zavq5tG8M$Kb zkxh&oZerI_`bG)LNAVveE}GCx36zTgWulZ%9c5Z2dmuwD?dYDR;2m>S`ZweD*y+9cP~^^K)h30LR! zL|o3VBsMl9OCU*UZuwqe3RvOYK+v#>I(54Jt_sw(3JJ(?u$PMBxFO#VR*OJ^bLT8V zIVc&vSSh5Gvd(3dd|uU9@uAXXHg?(tdrt!tB~WWm^5d`S_0eDUXXqR`i@Fy z;bXMHV`n#z(>E%ml2!)u5z*5$Yj2!ATCLqN5ss8D-&I6I6RkQM4qwrraP8Bpi$-~u zEL+Ekphi{^bvR<8mBY~^dgP*bq(nv4I#_{qODCwzDSETg!>y9wk!HY2D2^Gg9I`_# zRUl8hHoM-;FBdbSd7{LeF4A;@&7%%meu^8k6VZbP<{<-T$3=LLj9KerZE<>rh@y(F zWEMV-KM)9rvTBfsASL-6>^K|;B7RpZDr?UZKlGj|$Q24oCI?Lj0!%6Jj#8efE&3wP zJWJkA+9jd@%9ifYVM>gwY>3e1bl7njp38TkoNYD0KdGscv?&k8O##AZXO^Vis*{nJ zZ3n~~rlA((ysn9){r1Ni3$*v?q*N5r{>I{@CyR$?d`zc)jgkEXw=%HvgdsCPLLmOr z3*j(^BDop5kFF6-T1M}QH>$u~pv)sp66Sls?8^_k^i&?!K+=DDZ zY)048eeyug86s&emWHmIWW)3{q*51iMbq-cGl*hFQ*@|!vw9Cb@Wy4Gs@HSSOQEN9 zLx0K>M+fKq^lZ$$uzTNc*sLmCLIz>PI91fkHT?h@{7yggt z(P%H}Z&*FPv0k`lX-~gy?Yw8{#wNTHme*rzOU~BDYKUNvsBun5Y+J3$u$wA{~*v!#lj?Re@@ zan3$<8IgJ+9(()+VjH(^#P2MvuP4?$B6QSLDzCt<@fOx5l9QQM@-oaKqPsD1iFV>R z3Oj()dY*w}3M9Mr$lNW8hPB`{k??Wfo_>f1HkRTy*H<@hnH~zDL%9zcSOP|#l5f0P z0~?#M&6^t_kk&HbI7@q|NsV;hDWyYV)w!xf1| zk68#6BbV2^A)Hs>;>oEwB@+j_PIib9KZ8H(5HE>%#0d?k95a~Px0%}`%lyaKSuI?V(!}d7)+BwwI*~j!^4sl4h(9iTo`ajWsvHw#4 zLjUESAHqF9X!MF4xYyXk6k?D<{1yqwD7o<$L&1l z<#7j(eY~@a7rJ@e!wbE<(8miWc%h#c26*8lFAVa+5HFnK1(6qqd0~VXPV<7F7tZj) zC@+lh!dYGz=Uo##KF4E;$CEq`y4o?%O!Ii2$D;#`D+rEU=kX05CwTn2s{^K=;qiC# z&d>6W?{U2a)6Zev^Szkwd>ixlM__yb^THPaf~g+|^e15aB(VH6ko>&o%P{>4jGuw& zSAqNvz+ZszOE7)~#;?Kn4H#d8@jEbn5617qcm(4QVEiF=y@MIopF)DV{sQFx8oLc9>k z0v(;?(?ver2)R)Yg6ixA@s^f8ggO|<3Ht3P8VAVdBu1ly7>x~KboLZR<03{A!x)_# z!ALrdQNWMU)ESJXM=_ci!{|KG$FSJLA~cIJI5Y?Ga85@xbQK(97$@(phN`QmTFbTo zg<*lRPz`OLl>#+&d|wLf1Ms7FeGt8b_q-6S6yDoUoW+#h){YSHwpG=Z+VUtSbkO2p zFx3kyQznIFz-lj0CX((@@?0p%!oSzh;NW1^1Irx7F1P8E6P)$5Y@bSYN;6VN;*7nkLaE>rEznM7$RkeGNWeBdE^i6!=V97Y3R$*_oI`FzaR+kLQdZ?S0^dx^)rO zWPeQ!Pz|+*dmq%2{c5oL+x0py_i#@!OXn&3Gnjjc<_XH&N0^5!%_Od2Fh7hN68}LZ z`pA}c5JoB5;DUt{&$K(xYyQUOmSWNszvCwMvwm)iNu1(=XTVNdrxb6b zchOGUNps^4cdJWUvUg+2<{5mZie-CwsIh#tvAkk0pK5H(s!bCcv}-nP*aNh|ZIRMe zrKK$g2`Pw2$E26UWGErJfoa4IOyY4SKO?OLnPfOQwdG8REbyHsneiXxlU|lyA-r3R z^pdoGlevjaUVpPQR^^<5YqIqroat95J_uE}*=sDy`YhO4y@|5j)h(3msfvE+E7Ham zo9(Yke(AI{Ds4(PrL8Szc7XJyZBO-@2WPvg(yNJ+xGKE{U+62hs!3LzwptF%F@UPt zU6tNgK8@v>YE|_m{1`x|3AzZ7ZbzMo5vD3_M|lcA85JnJ6LnFTjJhe5qaF%V(H08R z(N+pG(KZT|Xgh^!)Jx&rXa|MasE@*2w3EV5MY||`GulmIKH5WJA=*n}G1^C=7Ck{> zDcVorr=tTDmZK*r)T4tG8qpyNE74OF-iwM9?nH+vydNE*a5s9I!taXu0dAj9I@H;! zI+!?5+Q6v8;r8@HFP^(LhxR^vyggsLHjfh*@OI>JB0`SP7|VIQUuDSNeHc=3t$?c& zPt+3+)9gWtl}`w);Hav7Rn^;dmbQOv?!o6_2YUTT4eU#MNA|^!vVB)U!jHNhAOyS7 zv-}B^?FDYJ(`eT4eb3XN_D($UV>NIPL*s(OI|y1rk3+Aci6;}Ga|Oco04q$M$_xsQ zhQ{xIzDBX@47L3OFgdX={h+ygQTkz=xQMqe**Uw!TqV)>S<&~A=&$0p@mKIW7%kw- zaL1#wBw;0zuP2bN;k|-`W=+A53ygxR3eICFRq03ZKEWAMw$hK`eae@|gp`PaXMi}s z2k&VT-T?_O&06u1CFvpFZv@F+#WO3{(Sjp};9kE%9xQS478z%GkM|KK&ZIfh8?M=# z17bAA7T6}ozSQj4Pni=(^Mq}<%#kVebP-!9W1?7Hvch2#%MA6IBjNZ-I4O?C<7~vk z9gW9%6~F#A{vza*qY+&t5v{;m0M1croJnyHVL8P;R>!;lop%0n4%XwnWtTW4iLsJ&n3SI-(K2KP zw;%PGA<>LHReNORXe>}5BU7_zfuWJa$zxL@r>+&=T53=2FY|X-dl`h_BEdDwrB#!o8@FGM^WvQv(d~s9nQo_p(!VB9A6MMVV-fz9Qz2ByU zS3bht?^440$2&0PfV3~$X%`-PAoAt>o5UN2Pi;{!EN>xUvbSUGsO_vC<7@qgCq;tE1D&K9@l-{6`Z-b=GEI2cYC zJPZMb>p<*+gR_M`$33aRrPxpEsdoj?PKx#sw40*+1nr{eiIrM1^tFi&_|-686K!PF zM#cc)2$I}-MCx>#sVy@YWSB=j_{~(UEsx=qYVBb846_F5vr*{xEMS(i}c5Mf5k$-4o5J}J(k)4ILQg;y&^f`T*_IkrFb=vavm&? zI`*aa@IK*8@o>!f6b~x9pVaGc-uIqPO)iD`g@$>`e6h8$P1Ri zm^H=T3ow1po-*DEnEr!3b&#$r{i8i)z3`LQOEAvMkgvcEe7RodUAJ}!*KvTt$Vs&e+;%ru(R+p(KQB7lQ*oNiMK<^?NIWQq2!%VvK{`th6l~REIELe VJ6*{94-4e~I-)2b{{tG