use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::domain::Domain; use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::object::{ ClassObject, FunctionObject, NamespaceObject, Object, ScriptObject, TObject, }; use crate::avm2::scope::{Scope, ScopeChain}; use crate::avm2::script::Script; use crate::avm2::value::Value; use crate::avm2::Avm2; use crate::avm2::Error; use crate::avm2::Multiname; use crate::avm2::Namespace; use crate::avm2::QName; use crate::string::AvmString; use crate::tag_utils::{self, ControlFlow, SwfMovie, SwfSlice, SwfStream}; use gc_arena::{Collect, GcCell, MutationContext}; use std::sync::Arc; use swf::TagCode; mod array; mod boolean; mod class; mod date; mod error; pub mod flash; mod function; mod global_scope; mod int; mod json; mod math; mod namespace; mod number; mod object; mod qname; mod regexp; mod string; mod toplevel; mod r#uint; mod vector; mod xml; mod xml_list; pub(crate) const NS_RUFFLE_INTERNAL: &str = "https://ruffle.rs/AS3/impl/"; pub(crate) const NS_VECTOR: &str = "__AS3__.vec"; pub use flash::utils::NS_FLASH_PROXY; /// This structure represents all system builtin classes. #[derive(Clone, Collect)] #[collect(no_drop)] pub struct SystemClasses<'gc> { pub object: ClassObject<'gc>, pub function: ClassObject<'gc>, pub class: ClassObject<'gc>, pub global: ClassObject<'gc>, pub string: ClassObject<'gc>, pub boolean: ClassObject<'gc>, pub number: ClassObject<'gc>, pub int: ClassObject<'gc>, pub uint: ClassObject<'gc>, pub namespace: ClassObject<'gc>, pub array: ClassObject<'gc>, pub movieclip: ClassObject<'gc>, pub framelabel: ClassObject<'gc>, pub scene: ClassObject<'gc>, pub application_domain: ClassObject<'gc>, pub event: ClassObject<'gc>, pub fullscreenevent: ClassObject<'gc>, pub video: ClassObject<'gc>, pub xml: ClassObject<'gc>, pub xml_list: ClassObject<'gc>, pub display_object: ClassObject<'gc>, pub shape: ClassObject<'gc>, pub textfield: ClassObject<'gc>, pub textformat: ClassObject<'gc>, pub graphics: ClassObject<'gc>, pub loaderinfo: ClassObject<'gc>, pub bytearray: ClassObject<'gc>, pub stage: ClassObject<'gc>, pub sprite: ClassObject<'gc>, pub simplebutton: ClassObject<'gc>, pub regexp: ClassObject<'gc>, pub vector: ClassObject<'gc>, pub soundtransform: ClassObject<'gc>, pub soundchannel: ClassObject<'gc>, pub bitmap: ClassObject<'gc>, pub bitmapdata: ClassObject<'gc>, pub date: ClassObject<'gc>, pub qname: ClassObject<'gc>, pub sharedobject: ClassObject<'gc>, pub mouseevent: ClassObject<'gc>, pub progressevent: ClassObject<'gc>, pub textevent: ClassObject<'gc>, pub errorevent: ClassObject<'gc>, pub ioerrorevent: ClassObject<'gc>, pub securityerrorevent: ClassObject<'gc>, pub transform: ClassObject<'gc>, pub colortransform: ClassObject<'gc>, pub matrix: ClassObject<'gc>, pub illegaloperationerror: ClassObject<'gc>, pub eventdispatcher: ClassObject<'gc>, pub rectangle: ClassObject<'gc>, pub keyboardevent: ClassObject<'gc>, pub point: ClassObject<'gc>, pub rangeerror: ClassObject<'gc>, pub referenceerror: ClassObject<'gc>, pub argumenterror: ClassObject<'gc>, } impl<'gc> SystemClasses<'gc> { /// Construct a minimal set of system classes necessary for bootstrapping /// player globals. /// /// All other system classes aside from the three given here will be set to /// the empty object also handed to this function. It is the caller's /// responsibility to instantiate each class and replace the empty object /// with that. fn new( object: ClassObject<'gc>, function: ClassObject<'gc>, class: ClassObject<'gc>, global: ClassObject<'gc>, ) -> Self { SystemClasses { object, function, class, global, // temporary initialization string: object, boolean: object, number: object, int: object, uint: object, namespace: object, array: object, movieclip: object, framelabel: object, scene: object, application_domain: object, event: object, fullscreenevent: object, video: object, xml: object, xml_list: object, display_object: object, shape: object, textfield: object, textformat: object, graphics: object, loaderinfo: object, bytearray: object, stage: object, sprite: object, simplebutton: object, regexp: object, vector: object, soundtransform: object, soundchannel: object, bitmap: object, bitmapdata: object, date: object, qname: object, sharedobject: object, mouseevent: object, progressevent: object, textevent: object, errorevent: object, ioerrorevent: object, securityerrorevent: object, transform: object, colortransform: object, matrix: object, illegaloperationerror: object, eventdispatcher: object, rectangle: object, keyboardevent: object, point: object, rangeerror: object, referenceerror: object, argumenterror: object, } } } /// Add a free-function builtin to the global scope. fn function<'gc>( activation: &mut Activation<'_, 'gc, '_>, package: impl Into>, name: &'static str, nf: NativeMethodImpl, script: Script<'gc>, ) -> Result<(), Error<'gc>> { let (_, mut global, mut domain) = script.init(); let mc = activation.context.gc_context; let scope = activation.create_scopechain(); let qname = QName::new(Namespace::package(package), name); let method = Method::from_builtin(nf, name, mc); let as3fn = FunctionObject::from_method(activation, method, scope, None, None).into(); domain.export_definition(qname, script, mc)?; global.install_const_late(mc, qname, as3fn, activation.avm2().classes().function); Ok(()) } /// Add a fully-formed class object builtin to the global scope. /// /// This allows the caller to pre-populate the class's prototype with dynamic /// properties, if necessary. fn dynamic_class<'gc>( mc: MutationContext<'gc, '_>, class_object: ClassObject<'gc>, script: Script<'gc>, // The `ClassObject` of the `Class` class class_class: ClassObject<'gc>, ) -> Result<(), Error<'gc>> { let (_, mut global, mut domain) = script.init(); let class = class_object.inner_class_definition(); let name = class.read().name(); global.install_const_late(mc, name, class_object.into(), class_class); domain.export_definition(name, script, mc) } /// Add a class builtin to the global scope. /// /// This function returns the class object and class prototype as a class, which /// may be stored in `SystemClasses` fn class<'gc>( activation: &mut Activation<'_, 'gc, '_>, class_def: GcCell<'gc, Class<'gc>>, script: Script<'gc>, ) -> Result, Error<'gc>> { let (_, mut global, mut domain) = script.init(); let class_read = class_def.read(); let super_class = if let Some(sc_name) = class_read.super_class_name() { let super_class: Result, Error<'gc>> = activation .resolve_definition(sc_name) .ok() .and_then(|v| v) .and_then(|v| v.as_object()) .ok_or_else(|| { format!( "Could not resolve superclass {} when defining global class {}", sc_name.to_qualified_name(activation.context.gc_context), class_read .name() .to_qualified_name(activation.context.gc_context) ) .into() }); let super_class = super_class? .as_class_object() .ok_or_else(|| Error::from("Base class of a global class is not a class"))?; Some(super_class) } else { None }; let class_name = class_read.name(); drop(class_read); let class_object = ClassObject::from_class(activation, class_def, super_class)?; global.install_const_late( activation.context.gc_context, class_name, class_object.into(), activation.avm2().classes().class, ); domain.export_definition(class_name, script, activation.context.gc_context)?; Ok(class_object) } /// Add a builtin constant to the global scope. fn constant<'gc>( mc: MutationContext<'gc, '_>, package: impl Into>, name: impl Into>, value: Value<'gc>, script: Script<'gc>, class: ClassObject<'gc>, ) -> Result<(), Error<'gc>> { let (_, mut global, mut domain) = script.init(); let name = QName::new(Namespace::package(package), name); domain.export_definition(name, script, mc)?; global.install_const_late(mc, name, value, class); Ok(()) } fn namespace<'gc>( activation: &mut Activation<'_, 'gc, '_>, package: impl Into>, name: impl Into>, uri: impl Into>, script: Script<'gc>, ) -> Result<(), Error<'gc>> { let namespace = NamespaceObject::from_namespace(activation, Namespace::Namespace(uri.into()))?; constant( activation.context.gc_context, package, name, namespace.into(), script, activation.avm2().classes().namespace, ) } macro_rules! avm2_system_class { ($field:ident, $activation:ident, $class:expr, $script:expr) => { let class_object = class($activation, $class, $script)?; let sc = $activation.avm2().system_classes.as_mut().unwrap(); sc.$field = class_object; }; } /// Initialize the player global domain. /// /// This should be called only once, to construct the global scope of the /// player. It will return a list of prototypes it has created, which should be /// stored on the AVM. All relevant declarations will also be attached to the /// given domain. pub fn load_player_globals<'gc>( activation: &mut Activation<'_, 'gc, '_>, domain: Domain<'gc>, ) -> Result<(), Error<'gc>> { let mc = activation.context.gc_context; let globals = ScriptObject::custom_object(activation.context.gc_context, None, None); let gs = ScopeChain::new(domain).chain(mc, &[Scope::new(globals)]); let script = Script::empty_script(mc, globals, domain); // Set the outer scope of this activation to the global scope. activation.set_outer(gs); // public / root package // // This part of global initialization is very complicated, because // everything has to circularly reference everything else: // // - Object is an instance of itself, as well as it's prototype // - All other types are instances of Class, which is an instance of // itself // - Function's prototype is an instance of itself // - All methods created by the above-mentioned classes are also instances // of Function // - All classes are put on Global's trait list, but Global needs // to be initialized first, but you can't do that until Object/Class are ready. // // Hence, this ridiculously complicated dance of classdef, type allocation, // and partial initialization. let object_classdef = object::create_class(mc); let object_class = ClassObject::from_class_partial(activation, object_classdef, None)?; let object_proto = ScriptObject::custom_object(mc, Some(object_class), None); let fn_classdef = function::create_class(mc); let fn_class = ClassObject::from_class_partial(activation, fn_classdef, Some(object_class))?; let fn_proto = ScriptObject::custom_object(mc, Some(fn_class), Some(object_proto)); let class_classdef = class::create_class(mc); let class_class = ClassObject::from_class_partial(activation, class_classdef, Some(object_class))?; let class_proto = ScriptObject::custom_object(mc, Some(object_class), Some(object_proto)); let global_classdef = global_scope::create_class(mc); let global_class = ClassObject::from_class_partial(activation, global_classdef, Some(object_class))?; let global_proto = ScriptObject::custom_object(mc, Some(object_class), Some(object_proto)); // Now to weave the Gordian knot... object_class.link_prototype(activation, object_proto)?; object_class.link_type(activation, class_proto, class_class); fn_class.link_prototype(activation, fn_proto)?; fn_class.link_type(activation, class_proto, class_class); class_class.link_prototype(activation, class_proto)?; class_class.link_type(activation, class_proto, class_class); global_class.link_prototype(activation, global_proto)?; global_class.link_type(activation, class_proto, class_class); // At this point, we need at least a partial set of system classes in // order to continue initializing the player. The rest of the classes // are set to a temporary class until we have a chance to initialize them. activation.context.avm2.system_classes = Some(SystemClasses::new( object_class, fn_class, class_class, global_class, )); // Our activation environment is now functional enough to finish // initializing the core class weave. The order of initialization shouldn't // matter here, as long as all the initialization machinery can see and // link the various system types together correctly. let class_class = class_class.into_finished_class(activation)?; let fn_class = fn_class.into_finished_class(activation)?; let object_class = object_class.into_finished_class(activation)?; let _global_class = global_class.into_finished_class(activation)?; globals.set_proto(mc, global_proto); globals.set_instance_of(mc, global_class); globals.fork_vtable(activation.context.gc_context); // From this point, `globals` is safe to be modified dynamic_class(mc, object_class, script, class_class)?; dynamic_class(mc, fn_class, script, class_class)?; dynamic_class(mc, class_class, script, class_class)?; // After this point, it is safe to initialize any other classes. // Make sure to initialize superclasses *before* their subclasses! avm2_system_class!(string, activation, string::create_class(mc), script); avm2_system_class!(boolean, activation, boolean::create_class(mc), script); avm2_system_class!(number, activation, number::create_class(mc), script); avm2_system_class!(int, activation, int::create_class(mc), script); avm2_system_class!(uint, activation, uint::create_class(mc), script); avm2_system_class!(namespace, activation, namespace::create_class(mc), script); avm2_system_class!(qname, activation, qname::create_class(mc), script); avm2_system_class!(array, activation, array::create_class(mc), script); function(activation, "", "trace", toplevel::trace, script)?; function(activation, "", "isFinite", toplevel::is_finite, script)?; function(activation, "", "isNaN", toplevel::is_nan, script)?; function(activation, "", "parseInt", toplevel::parse_int, script)?; function(activation, "", "parseFloat", toplevel::parse_float, script)?; function(activation, "", "escape", toplevel::escape, script)?; class(activation, json::create_class(mc), script)?; avm2_system_class!(regexp, activation, regexp::create_class(mc), script); avm2_system_class!(vector, activation, vector::create_class(mc), script); avm2_system_class!(xml, activation, xml::create_class(mc), script); avm2_system_class!(xml_list, activation, xml_list::create_class(mc), script); avm2_system_class!(date, activation, date::create_class(mc), script); // package `flash.system` avm2_system_class!( application_domain, activation, flash::system::application_domain::create_class(mc), script ); class( activation, flash::events::ieventdispatcher::create_interface(mc), script, )?; avm2_system_class!( eventdispatcher, activation, flash::events::eventdispatcher::create_class(mc), script ); // package `flash.utils` class( activation, flash::utils::dictionary::create_class(mc), script, )?; namespace( activation, "flash.utils", "flash_proxy", flash::utils::NS_FLASH_PROXY, script, )?; class(activation, flash::utils::proxy::create_class(mc), script)?; // package `flash.display` class( activation, flash::display::ibitmapdrawable::create_interface(mc), script, )?; avm2_system_class!( display_object, activation, flash::display::displayobject::create_class(mc), script ); avm2_system_class!( shape, activation, flash::display::shape::create_class(mc), script ); class( activation, flash::display::interactiveobject::create_class(mc), script, )?; avm2_system_class!( simplebutton, activation, flash::display::simplebutton::create_class(mc), script ); class( activation, flash::display::displayobjectcontainer::create_class(mc), script, )?; avm2_system_class!( sprite, activation, flash::display::sprite::create_class(mc), script ); avm2_system_class!( movieclip, activation, flash::display::movieclip::create_class(mc), script ); avm2_system_class!( framelabel, activation, flash::display::framelabel::create_class(mc), script ); avm2_system_class!( graphics, activation, flash::display::graphics::create_class(mc), script ); avm2_system_class!( loaderinfo, activation, flash::display::loaderinfo::create_class(mc), script ); avm2_system_class!( stage, activation, flash::display::stage::create_class(mc), script ); avm2_system_class!( bitmap, activation, flash::display::bitmap::create_class(mc), script ); avm2_system_class!( bitmapdata, activation, flash::display::bitmapdata::create_class(mc), script ); // package `flash.geom` // package `flash.media` avm2_system_class!( video, activation, flash::media::video::create_class(mc), script ); class(activation, flash::media::sound::create_class(mc), script)?; avm2_system_class!( soundtransform, activation, flash::media::soundtransform::create_class(mc), script ); class( activation, flash::media::soundmixer::create_class(mc), script, )?; avm2_system_class!( soundchannel, activation, flash::media::soundchannel::create_class(mc), script ); // package `flash.ui` class(activation, flash::ui::mouse::create_class(mc), script)?; class(activation, flash::ui::keyboard::create_class(mc), script)?; // package `flash.net` avm2_system_class!( sharedobject, activation, flash::net::sharedobject::create_class(mc), script ); class( activation, flash::net::object_encoding::create_class(mc), script, )?; // package `flash.text` avm2_system_class!( textfield, activation, flash::text::textfield::create_class(mc), script ); avm2_system_class!( textformat, activation, flash::text::textformat::create_class(mc), script ); class(activation, flash::text::font::create_class(mc), script)?; // package `flash.crypto` // package `flash.external` class( activation, flash::external::externalinterface::create_class(mc), script, )?; // Inside this call, the macro `avm2_system_classes_playerglobal` // triggers classloading. Therefore, we run `load_playerglobal` // relative late, so that it can access classes defined before // this call. load_playerglobal(activation, domain)?; Ok(()) } /// This file is built by 'core/build_playerglobal/' /// See that tool, and 'core/src/avm2/globals/README.md', for more details const PLAYERGLOBAL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/playerglobal.swf")); mod native { include!(concat!(env!("OUT_DIR"), "/native_table.rs")); } /// Loads classes from our custom 'playerglobal' (which are written in ActionScript) /// into the environment. See 'core/src/avm2/globals/README.md' for more information fn load_playerglobal<'gc>( activation: &mut Activation<'_, 'gc, '_>, domain: Domain<'gc>, ) -> Result<(), Error<'gc>> { activation.avm2().native_method_table = native::NATIVE_METHOD_TABLE; activation.avm2().native_instance_allocator_table = native::NATIVE_INSTANCE_ALLOCATOR_TABLE; let movie = Arc::new(SwfMovie::from_data(PLAYERGLOBAL, None, None)?); let slice = SwfSlice::from(movie); let mut reader = slice.read_from(0); let tag_callback = |reader: &mut SwfStream<'_>, tag_code, _tag_len| { if tag_code == TagCode::DoAbc { let do_abc = reader .read_do_abc() .expect("playerglobal.swf should be valid"); Avm2::do_abc(&mut activation.context, do_abc, domain) .expect("playerglobal.swf should be valid"); } else if tag_code != TagCode::End { panic!( "playerglobal should only contain `DoAbc` tag - found tag {:?}", tag_code ) } Ok(ControlFlow::Continue) }; let _ = tag_utils::decode_tags(&mut reader, tag_callback); macro_rules! avm2_system_classes_playerglobal { ($activation:expr, $script:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => { $( let name = Multiname::new(Namespace::package($package), $class_name); let class_object = activation.resolve_class(&name)?; let sc = $activation.avm2().system_classes.as_mut().unwrap(); sc.$field = class_object; )* } } // This acts the same way as 'avm2_system_class', but for classes // declared in 'playerglobal'. Classes are declared as ("package", "class", field_name), // and are stored in 'avm2().system_classes' avm2_system_classes_playerglobal!( activation, script, [ ("", "ArgumentError", argumenterror), ("", "RangeError", rangeerror), ("", "ReferenceError", referenceerror), ("flash.display", "Scene", scene), ( "flash.errors", "IllegalOperationError", illegaloperationerror ), ("flash.events", "Event", event), ("flash.events", "TextEvent", textevent), ("flash.events", "ErrorEvent", errorevent), ("flash.events", "KeyboardEvent", keyboardevent), ("flash.events", "ProgressEvent", progressevent), ("flash.events", "SecurityErrorEvent", securityerrorevent), ("flash.events", "IOErrorEvent", ioerrorevent), ("flash.events", "MouseEvent", mouseevent), ("flash.events", "FullScreenEvent", fullscreenevent), ("flash.geom", "Matrix", matrix), ("flash.geom", "Point", point), ("flash.geom", "Rectangle", rectangle), ("flash.geom", "Transform", transform), ("flash.geom", "ColorTransform", colortransform), ("flash.utils", "ByteArray", bytearray), ] ); // Domain memory must be initialized after playerglobals is loaded because it relies on ByteArray. domain.init_default_domain_memory(activation)?; Ok(()) }