Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
Dinnerbone authored Jun 21, 2024
2 parents 133557a + dc03f54 commit 8ee6a39
Show file tree
Hide file tree
Showing 79 changed files with 4,956 additions and 1,072 deletions.
2 changes: 1 addition & 1 deletion core/src/avm1/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
234 changes: 217 additions & 17 deletions core/src/avm1/globals/local_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<LocalConnectionHandle>>,
}

#[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<Self> {
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<AmfValue>,
) -> 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>(
Expand All @@ -18,29 +143,104 @@ pub fn domain<'gc>(
_args: &[Value<'gc>],
) -> Result<Value<'gc>, 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(AvmString::new_utf8(
activation.context.gc_context,
domain,
)))
}

pub fn connect<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, 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<Value<'gc>, 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());
};

Ok(Value::String(domain))
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<Value<'gc>, 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>,
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
this.set_native(
activation.gc(),
NativeObject::LocalConnection(LocalConnection(Gc::new(
activation.gc(),
LocalConnectionData {
handle: RefCell::new(None),
},
))),
);
Ok(this.into())
}

Expand Down
2 changes: 2 additions & 0 deletions core/src/avm1/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
12 changes: 4 additions & 8 deletions core/src/avm2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ mod property_map;
mod qname;
mod regexp;
mod scope;
mod script;
pub mod script;
#[cfg(feature = "known_stubs")]
pub mod specification;
mod string;
Expand Down Expand Up @@ -580,7 +580,7 @@ impl<'gc> Avm2<'gc> {
flags: DoAbc2Flag,
domain: Domain<'gc>,
movie: Arc<SwfMovie>,
) -> Result<(), Error<'gc>> {
) -> Result<Option<Script<'gc>>, Error<'gc>> {
let mut reader = Reader::new(data);
let abc = match reader.read() {
Ok(abc) => abc,
Expand All @@ -604,13 +604,9 @@ impl<'gc> Avm2<'gc> {
}

if !flags.contains(DoAbc2Flag::LAZY_INITIALIZE) {
for i in 0..num_scripts {
if let Some(script) = tunit.get_script(i) {
script.globals(&mut activation.context)?;
}
}
return Ok(Some(tunit.get_script(num_scripts - 1).unwrap()));
}
Ok(())
Ok(None)
}

pub fn stage_domain(&self) -> Domain<'gc> {
Expand Down
31 changes: 18 additions & 13 deletions core/src/avm2/amf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 len = o.as_array_storage().unwrap().length() as u32;

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);
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);
}
}
}

let len = o.as_array_storage().unwrap().length() as u32;
Some(AmfValue::ECMAArray(dense, sparse, len))
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()) {
Expand Down
5 changes: 3 additions & 2 deletions core/src/avm2/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,12 +293,13 @@ impl<'gc> Domain<'gc> {
}
// FIXME - is this the correct api version?
let api_version = activation.avm2().root_api_version;
let name = QName::from_qualified_name(name, api_version, activation);
let name = QName::from_qualified_name(name, api_version, &mut activation.context);

let res = self.get_defined_value(activation, name);

if let Some(type_name) = type_name {
let type_qname = QName::from_qualified_name(type_name, api_version, activation);
let type_qname =
QName::from_qualified_name(type_name, api_version, &mut activation.context);
let type_class = self.get_defined_value(activation, type_qname)?;
if let Ok(res) = res {
let class = res.as_object().ok_or_else(|| {
Expand Down
Loading

0 comments on commit 8ee6a39

Please sign in to comment.