diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index e3ef2ee50..2ea5d58f9 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -710,6 +710,11 @@ pub fn load_player_globals<'gc>( activation, flash::display::capsstyle::create_class(mc), implicit_deriver, + )?; + class( + activation, + flash::display::loaderinfo::create_class(mc), + flash::display::loaderinfo::loaderinfo_deriver, domain, script, )?; diff --git a/core/src/avm2/globals/flash/display.rs b/core/src/avm2/globals/flash/display.rs index 7ce90c049..11595ee06 100644 --- a/core/src/avm2/globals/flash/display.rs +++ b/core/src/avm2/globals/flash/display.rs @@ -8,6 +8,7 @@ pub mod graphics; pub mod interactiveobject; pub mod jointstyle; pub mod linescalemode; +pub mod loaderinfo; pub mod movieclip; pub mod scene; pub mod shape; diff --git a/core/src/avm2/globals/flash/display/loaderinfo.rs b/core/src/avm2/globals/flash/display/loaderinfo.rs new file mode 100644 index 000000000..f975e658f --- /dev/null +++ b/core/src/avm2/globals/flash/display/loaderinfo.rs @@ -0,0 +1,56 @@ +//! `flash.display.LoaderInfo` builtin/prototype + +use crate::avm2::activation::Activation; +use crate::avm2::class::{Class, ClassAttributes}; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::{LoaderInfoObject, Object}; +use crate::avm2::scope::Scope; +use crate::avm2::value::Value; +use crate::avm2::Error; +use gc_arena::{GcCell, MutationContext}; + +/// Implements `flash.display.LoaderInfo`'s instance constructor. +pub fn instance_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Err("LoaderInfo cannot be constructed".into()) +} + +/// Implements `flash.display.LoaderInfo`'s class constructor. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Derive `LoaderInfoObject` impls. +pub fn loaderinfo_deriver<'gc>( + base_proto: Object<'gc>, + activation: &mut Activation<'_, 'gc, '_>, + class: GcCell<'gc, Class<'gc>>, + scope: Option>>, +) -> Result, Error> { + LoaderInfoObject::derive(base_proto, activation.context.gc_context, class, scope) +} + +/// Construct `LoaderInfo`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + let class = Class::new( + QName::new(Namespace::package("flash.display"), "LoaderInfo"), + Some(QName::new(Namespace::package("flash.events"), "EventDispatcher").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ); + + let mut write = class.write(mc); + + write.set_attributes(ClassAttributes::SEALED); + + class +} diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index a60930067..650af2058 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -27,6 +27,7 @@ mod dispatch_object; mod domain_object; mod event_object; mod function_object; +mod loaderinfo_object; mod namespace_object; mod primitive_object; mod regexp_object; @@ -40,6 +41,7 @@ pub use crate::avm2::object::dispatch_object::DispatchObject; pub use crate::avm2::object::domain_object::DomainObject; pub use crate::avm2::object::event_object::EventObject; pub use crate::avm2::object::function_object::{implicit_deriver, FunctionObject}; +pub use crate::avm2::object::loaderinfo_object::LoaderInfoObject; pub use crate::avm2::object::namespace_object::NamespaceObject; pub use crate::avm2::object::primitive_object::PrimitiveObject; pub use crate::avm2::object::regexp_object::RegExpObject; @@ -64,7 +66,8 @@ pub use crate::avm2::object::xml_object::XmlObject; DispatchObject(DispatchObject<'gc>), XmlObject(XmlObject<'gc>), RegExpObject(RegExpObject<'gc>), - ByteArrayObject(ByteArrayObject<'gc>) + ByteArrayObject(ByteArrayObject<'gc>), + LoaderInfoObject(LoaderInfoObject<'gc>), } )] pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy { diff --git a/core/src/avm2/object/loaderinfo_object.rs b/core/src/avm2/object/loaderinfo_object.rs new file mode 100644 index 000000000..1d14f75bb --- /dev/null +++ b/core/src/avm2/object/loaderinfo_object.rs @@ -0,0 +1,136 @@ +//! Loader-info object + +use crate::avm2::activation::Activation; +use crate::avm2::class::Class; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::script_object::{ScriptObjectClass, ScriptObjectData}; +use crate::avm2::object::{Object, ObjectPtr, TObject}; +use crate::avm2::scope::Scope; +use crate::avm2::string::AvmString; +use crate::avm2::traits::Trait; +use crate::avm2::value::Value; +use crate::avm2::Error; +use crate::tag_utils::SwfMovie; +use crate::{impl_avm2_custom_object, impl_avm2_custom_object_properties}; +use gc_arena::{Collect, GcCell, MutationContext}; +use std::sync::Arc; + +/// Represents a thing which can be loaded by a loader. +#[derive(Collect, Debug, Clone)] +#[collect(no_drop)] +pub enum LoaderStream { + /// A loaded SWF movie. + SWF(Arc), +} + +/// An Object which represents a loadable object, such as a SWF movie or image +/// resource. +#[derive(Collect, Debug, Clone, Copy)] +#[collect(no_drop)] +pub struct LoaderInfoObject<'gc>(GcCell<'gc, LoaderInfoObjectData<'gc>>); + +#[derive(Collect, Debug, Clone)] +#[collect(no_drop)] +pub struct LoaderInfoObjectData<'gc> { + /// All normal script data. + base: ScriptObjectData<'gc>, + + /// The loaded stream that this gets it's info from. + loaded_stream: Option, +} + +impl<'gc> LoaderInfoObject<'gc> { + /// Box a movie into a loader info object. + pub fn from_movie( + movie: Arc, + base_proto: Object<'gc>, + mc: MutationContext<'gc, '_>, + ) -> Result, Error> { + let base = ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::NoClass); + let loaded_stream = Some(LoaderStream::SWF(movie)); + + Ok(LoaderInfoObject(GcCell::allocate( + mc, + LoaderInfoObjectData { + base, + loaded_stream, + }, + )) + .into()) + } + + /// Construct a loader-info subclass. + pub fn derive( + base_proto: Object<'gc>, + mc: MutationContext<'gc, '_>, + class: GcCell<'gc, Class<'gc>>, + scope: Option>>, + ) -> Result, Error> { + let base = ScriptObjectData::base_new( + Some(base_proto), + ScriptObjectClass::InstancePrototype(class, scope), + ); + + Ok(LoaderInfoObject(GcCell::allocate( + mc, + LoaderInfoObjectData { + base, + loaded_stream: None, + }, + )) + .into()) + } +} + +impl<'gc> TObject<'gc> for LoaderInfoObject<'gc> { + impl_avm2_custom_object!(base); + impl_avm2_custom_object_properties!(base); + + fn value_of(&self, mc: MutationContext<'gc, '_>) -> Result, Error> { + if let Some(class) = self.as_proto_class() { + Ok(AvmString::new(mc, format!("[object {}]", class.read().name().local_name())).into()) + } else { + Ok("[object Object]".into()) + } + } + + fn construct( + &self, + activation: &mut Activation<'_, 'gc, '_>, + _args: &[Value<'gc>], + ) -> Result, Error> { + let this: Object<'gc> = Object::LoaderInfoObject(*self); + let base = ScriptObjectData::base_new(Some(this), ScriptObjectClass::NoClass); + + Ok(LoaderInfoObject(GcCell::allocate( + activation.context.gc_context, + LoaderInfoObjectData { + base, + loaded_stream: None, + }, + )) + .into()) + } + + fn derive( + &self, + activation: &mut Activation<'_, 'gc, '_>, + class: GcCell<'gc, Class<'gc>>, + scope: Option>>, + ) -> Result, Error> { + let this: Object<'gc> = Object::LoaderInfoObject(*self); + let base = ScriptObjectData::base_new( + Some(this), + ScriptObjectClass::InstancePrototype(class, scope), + ); + + Ok(LoaderInfoObject(GcCell::allocate( + activation.context.gc_context, + LoaderInfoObjectData { + base, + loaded_stream: None, + }, + )) + .into()) + } +}