diff --git a/Cargo.lock b/Cargo.lock index afd1a3905..8ec761192 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,6 +264,9 @@ checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" name = "build_playerglobal" version = "0.1.0" dependencies = [ + "convert_case", + "proc-macro2", + "quote", "swf", ] @@ -574,6 +577,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "convert_case" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" + [[package]] name = "cookie-factory" version = "0.3.2" diff --git a/core/build_playerglobal/Cargo.toml b/core/build_playerglobal/Cargo.toml index 2623a4fe1..bf619d550 100644 --- a/core/build_playerglobal/Cargo.toml +++ b/core/build_playerglobal/Cargo.toml @@ -4,4 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +convert_case = "0.5.0" +proc-macro2 = "1.0.40" +quote = "1.0.20" swf = { path = "../../swf" } diff --git a/core/build_playerglobal/src/lib.rs b/core/build_playerglobal/src/lib.rs index 20f0edc45..4de0da1f4 100644 --- a/core/build_playerglobal/src/lib.rs +++ b/core/build_playerglobal/src/lib.rs @@ -1,9 +1,15 @@ //! An internal Ruffle utility to build our playerglobal //! `library.swf` +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream; +use quote::quote; use std::fs::File; -use std::path::PathBuf; +use std::io::Write; +use std::path::{Path, PathBuf}; use std::process::Command; +use std::str::FromStr; +use swf::avm2::types::*; use swf::DoAbc; use swf::Header; use swf::SwfStr; @@ -50,6 +56,8 @@ pub fn build_playerglobal( std::fs::remove_file(playerglobal.with_extension("cpp"))?; std::fs::remove_file(playerglobal.with_extension("h"))?; + write_native_table(&bytes, &out_dir)?; + let tags = vec![Tag::DoAbc(DoAbc { name: SwfStr::from_utf8_str(""), is_lazy_initialize: true, @@ -62,3 +70,184 @@ pub fn build_playerglobal( Ok(()) } + +// Resolve the 'name' field of a `Multiname`. This only handles the cases +// that we need for our custom `playerglobal.swf` ( +fn resolve_multiname_name<'a>(abc: &'a AbcFile, multiname: &Multiname) -> &'a str { + if let Multiname::QName { name, .. } | Multiname::Multiname { name, .. } = multiname { + &abc.constant_pool.strings[name.0 as usize - 1] + } else { + panic!("Unexpected Multiname {:?}", multiname); + } +} + +// Like `resolve_multiname_name`, but for namespaces instead. +fn resolve_multiname_ns<'a>(abc: &'a AbcFile, multiname: &Multiname) -> &'a str { + if let Multiname::QName { namespace, .. } = multiname { + let ns = &abc.constant_pool.namespaces[namespace.0 as usize - 1]; + if let Namespace::Package(p) = ns { + &abc.constant_pool.strings[p.0 as usize - 1] + } else { + panic!("Unexpected Namespace {:?}", ns); + } + } else { + panic!("Unexpected Multiname {:?}", multiname); + } +} + +/// Handles native functons defined in our `playerglobal` +/// +/// The high-level idea is to generate code (specifically, a `TokenStream`) +/// which builds a table - mapping from the method ids of native functions, +/// to Rust function pointers which implement them. +/// +/// This table gets used when we first load a method from an ABC file. +/// If it's a native method in our `playerglobal`, we swap it out +/// with a `NativeMethod` retrieved from the table. To the rest of +/// the Ruffle codebase, it appears as though the method was always defined +/// as a native method, and never existed in the bytecode at all. +/// +/// See `flash.system.Security.allowDomain` for an example of defining +/// and using a native method. +fn write_native_table(data: &[u8], out_dir: &Path) -> Result<(), Box> { + let mut reader = swf::avm2::read::Reader::new(data); + let abc = reader.read()?; + + let none_tokens = quote! { None }; + let mut rust_paths = vec![none_tokens; abc.methods.len()]; + + let mut check_trait = |trait_: &Trait, parent: Option>| { + let method_id = match trait_.kind { + TraitKind::Method { method, .. } + | TraitKind::Getter { method, .. } + | TraitKind::Setter { method, .. } => { + let abc_method = &abc.methods[method.0 as usize]; + // We only want to process native methods + if !abc_method.flags.contains(MethodFlags::NATIVE) { + return; + } + method + } + TraitKind::Function { .. } => { + panic!("TraitKind::Function is not supported: {:?}", trait_) + } + _ => return, + }; + + // Note - technically, this could conflict with + // a method with a name starting with `get_` or `set_`. + // However, all Flash methods are named with lowerCamelCase, + // so we'll never actually need to implement a native method that + // would cause such a conflict. + let method_prefix = match trait_.kind { + TraitKind::Getter { .. } => "get_", + TraitKind::Setter { .. } => "set_", + _ => "", + }; + + let flash_to_rust_path = |path: &str| { + // Convert each component of the path to snake-case. + // This correctly handles sequences of upper-case letters, + // so 'URLLoader' becomes 'url_loader' + let components = path + .split('.') + .map(|component| component.to_case(Case::Snake)) + .collect::>(); + // Form a Rust path from the snake-case components + components.join("::") + }; + + let mut path = "crate::avm2::globals::".to_string(); + + let trait_name = &abc.constant_pool.multinames[trait_.name.0 as usize - 1]; + + if let Some(parent) = parent { + // This is a method defined inside the class. Append the class namespace + // (the package) and the class name. + // For example, a namespace of "flash.system" and a name of "Security" + // turns into the path "flash::system::security" + let multiname = &abc.constant_pool.multinames[parent.0 as usize - 1]; + path += &flash_to_rust_path(resolve_multiname_ns(&abc, multiname)); + path += "::"; + path += &flash_to_rust_path(resolve_multiname_name(&abc, multiname)); + path += "::"; + } else { + // This is a freestanding function. Append its namespace (the package). + // For example, the freestanding function "flash.utils.getDefinitionByName" + // has a namespace of "flash.utils", which turns into the path + // "flash::utils" + path += &flash_to_rust_path(resolve_multiname_ns(&abc, trait_name)); + path += "::"; + } + + // Append the trait name - this corresponds to the actual method + // name (e.g. `getDefinitionByName`) + path += method_prefix; + path += &flash_to_rust_path(resolve_multiname_name(&abc, trait_name)); + + // Now that we've built up the path, convert it into a `TokenStream`. + // This gives us something like + // `crate::avm2::globals::flash::system::Security::allowDomain` + // + // The resulting `TokenStream` is suitable for usage with `quote!` to + // generate a reference to the function pointer that should exist + // at that path in Rust code. + let path_tokens = TokenStream::from_str(&path).unwrap(); + rust_paths[method_id.0 as usize] = quote! { Some(#path_tokens) }; + }; + + // We support three kinds of native methods: + // instance methods, class methods, and freestanding functions. + // We're going to insert them into an array indexed by `MethodId`, + // so it doesn't matter what order we visit them in. + for (i, instance) in abc.instances.iter().enumerate() { + // Look for native instance methods + for trait_ in &instance.traits { + check_trait(trait_, Some(instance.name)); + } + // Look for native class methods (in the corresponding + // `Class` definition) + for trait_ in &abc.classes[i].traits { + check_trait(trait_, Some(instance.name)); + } + } + + // Look for freestanding methods + for script in &abc.scripts { + for trait_ in &script.traits { + check_trait(trait_, None); + } + } + + // Finally, generate the actual code. This is a Rust array - + // the entry at index `i` is a Rust function pointer for the native + // method with id `i`. Not all methods in playerglobal will be native + // methods, so we store `None` in the entries corresponding to non-native + // functions. We expect the majority of the methods in playerglobal to be + // native, so this should only waste a small amount of memory. + // + // If a function pointer doesn't exist at the expected path, + // then Ruffle compilation will fail + // with an error message that mentions the non-existent path. + // + // When we initially load a method from an ABC file, we check if it's from our playerglobal, + // and if its ID exists in this table. + // If so, we replace it with a `NativeMethod` constructed + // from the function pointer we looked up in the table. + + let make_native_table = quote! { + const NATIVE_TABLE: &[Option] = &[ + #(#rust_paths,)* + ]; + } + .to_string(); + + // Each table entry ends with ') ,' - insert a newline so that + // each entry is on its own line. This makes error messages more readable. + let make_native_table = make_native_table.replace(") ,", ") ,\n"); + + let mut native_table_file = File::create(out_dir.join("native_table.rs"))?; + native_table_file.write_all(make_native_table.as_bytes())?; + + Ok(()) +} diff --git a/core/src/avm2.rs b/core/src/avm2.rs index bfbbcbb3b..eccbce810 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,7 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::globals::SystemClasses; -use crate::avm2::method::Method; +use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::object::EventObject; use crate::avm2::script::{Script, TranslationUnit}; use crate::context::UpdateContext; @@ -75,6 +75,9 @@ pub struct Avm2<'gc> { /// System classes. system_classes: Option>, + #[collect(require_static)] + native_table: &'static [Option], + /// A list of objects which are capable of recieving broadcasts. /// /// Certain types of events are "broadcast events" that are emitted on all @@ -98,6 +101,7 @@ impl<'gc> Avm2<'gc> { stack: Vec::new(), globals, system_classes: None, + native_table: Default::default(), broadcast_list: Default::default(), #[cfg(feature = "avm_debug")] @@ -130,7 +134,7 @@ impl<'gc> Avm2<'gc> { Method::Native(method) => { //This exists purely to check if the builtin is OK with being called with //no parameters. - init_activation.resolve_parameters(method.name, &[], &method.signature)?; + init_activation.resolve_parameters(&method.name, &[], &method.signature)?; (method.method)(&mut init_activation, Some(scope), &[])?; } diff --git a/core/src/avm2/domain.rs b/core/src/avm2/domain.rs index 1069cff7a..40360ac75 100644 --- a/core/src/avm2/domain.rs +++ b/core/src/avm2/domain.rs @@ -53,6 +53,10 @@ impl<'gc> Domain<'gc> { )) } + pub fn is_avm2_global_domain(&self, activation: &mut Activation<'_, 'gc, '_>) -> bool { + activation.avm2().global_domain().0.as_ptr() == self.0.as_ptr() + } + /// Create a new domain with a given parent. /// /// This function must not be called before the player globals have been diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index a4a640828..0fe26462b 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -132,7 +132,7 @@ impl<'gc> Executable<'gc> { } let arguments = activation.resolve_parameters( - bm.method.name, + &bm.method.name, arguments, &bm.method.signature, )?; diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 54c653198..4349d58c3 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -443,11 +443,6 @@ pub fn load_player_globals<'gc>( flash::system::capabilities::create_class(mc), script, )?; - class( - activation, - flash::system::security::create_class(mc), - script, - )?; class(activation, flash::system::system::create_class(mc), script)?; // package `flash.events` @@ -580,14 +575,6 @@ pub fn load_player_globals<'gc>( script, )?; - function( - activation, - "flash.utils", - "getDefinitionByName", - flash::utils::get_definition_by_name, - script, - )?; - // package `flash.display` class( activation, @@ -756,12 +743,6 @@ pub fn load_player_globals<'gc>( flash::net::object_encoding::create_class(mc), script, )?; - class(activation, flash::net::url_loader::create_class(mc), script)?; - class( - activation, - flash::net::url_request::create_class(mc), - script, - )?; // package `flash.text` avm2_system_class!( @@ -806,12 +787,17 @@ pub fn load_player_globals<'gc>( /// See that tool, and 'core/src/avm2/globals/README.md', for more details const PLAYERGLOBAL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/playerglobal.swf")); +// This defines a const named `NATIVE_TABLE` +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> { + activation.avm2().native_table = NATIVE_TABLE; + let movie = Arc::new(SwfMovie::from_data(PLAYERGLOBAL, None, None)?); let slice = SwfSlice::from(movie); diff --git a/core/src/avm2/globals/README.md b/core/src/avm2/globals/README.md index 4858c861f..b2806610f 100644 --- a/core/src/avm2/globals/README.md +++ b/core/src/avm2/globals/README.md @@ -32,6 +32,26 @@ In addition to potential copyright issues around redistributing Flash's `playerg many of its classes rely on specific 'native' methods being provided by the Flash VM, which Ruffle does not implement. +## Native methods + +We support defining native methods (instance methods, class methods, and freestanding functions) +in ActionScript classes in playerglobal. During the build process, we automatically +generate a reference to a Rust function at the corresponding path in Ruffle. + +For example, the native method function `flash.system.Security.allowDomain` +expects a Rust function to be defined at `crate::avm2::globals::flash::system::security::allowDomain`. + +This function is cast to a `NativeMethodImpl` function pointer, exactly like +functions defined on a pure-Rust class definition. + +If you're unsure of the path to use, just build Ruffle after marking the +`ActionScript` method as `native` - the compiler will produce an error +explaining where the Rust function needs to be defined. + +The ActionScript method and the Rust function are automatically linked +together, and the Rust function will be invoked when the corresponding +function is called from ActionScript. + ## Compiling Java must be installed for the build process to complete. @@ -53,8 +73,6 @@ when any of our ActionScript classes are changed. ## Limitations -* Only pure ActionScript classes are currently supported. Classes with -'native' methods are not yet supported. * 'Special' classes which are loaded early during player initialization (e.g. `Object`, `Function`, `Class`) cannot currently be implemented in `playerglobal`, since they are initialized in a special diff --git a/core/src/avm2/globals/date.rs b/core/src/avm2/globals/date.rs index 04c314e40..092d4c16e 100644 --- a/core/src/avm2/globals/date.rs +++ b/core/src/avm2/globals/date.rs @@ -306,7 +306,7 @@ pub fn class_init<'gc>( &Multiname::public(*name), FunctionObject::from_method( activation, - Method::from_builtin(*method, name, gc_context), + Method::from_builtin(*method, *name, gc_context), scope, None, Some(this_class), diff --git a/core/src/avm2/globals/flash/events/EventDispatcher.as b/core/src/avm2/globals/flash/events/EventDispatcher.as new file mode 100644 index 000000000..fb959d863 --- /dev/null +++ b/core/src/avm2/globals/flash/events/EventDispatcher.as @@ -0,0 +1,5 @@ +// This is a stub - the actual class is defined in `eventdispatcher.rs` +package flash.events { + public class EventDispatcher { + } +} diff --git a/core/src/avm2/globals/flash/net.rs b/core/src/avm2/globals/flash/net.rs index 9e109c5ea..ec7db62ce 100644 --- a/core/src/avm2/globals/flash/net.rs +++ b/core/src/avm2/globals/flash/net.rs @@ -3,4 +3,3 @@ pub mod object_encoding; pub mod sharedobject; pub mod url_loader; -pub mod url_request; diff --git a/core/src/avm2/globals/flash/net/URLLoader.as b/core/src/avm2/globals/flash/net/URLLoader.as new file mode 100644 index 000000000..13f20e6ed --- /dev/null +++ b/core/src/avm2/globals/flash/net/URLLoader.as @@ -0,0 +1,29 @@ +package flash.net { + import flash.events.EventDispatcher; + import flash.net.URLRequest; + public class URLLoader extends EventDispatcher { + public var data: *; + public var dataFormat: String = "text"; + + public function URLLoader(request:URLRequest = null) { + if (request != null) { + this.load(request); + } + } + + // FIXME - this should be a normal property for consistency with Flash + public function get bytesTotal():uint { + if (this.data) { + return this.data.length; + } + return 0; + } + + // FIXME - this should be a normal property for consistency with Flash + public function get bytesLoaded():uint { + // TODO - update this as the download progresses + return this.bytesTotal + } + public native function load(request:URLRequest):void; + } +} diff --git a/core/src/avm2/globals/flash/net/URLRequest.as b/core/src/avm2/globals/flash/net/URLRequest.as new file mode 100644 index 000000000..438399171 --- /dev/null +++ b/core/src/avm2/globals/flash/net/URLRequest.as @@ -0,0 +1,12 @@ +package flash.net { + public final class URLRequest { + // NOTE - when implementing properties (e.g. `contentType`, `data`, etc.) + // be sure to also check for them in `URLLoader` + + // FIXME - this should be a getter/setter for consistency with Flash + public var url:String; + public function URLRequest(url:String = null) { + this.url = url; + } + } +} diff --git a/core/src/avm2/globals/flash/net/url_loader.rs b/core/src/avm2/globals/flash/net/url_loader.rs index 9c793d9f6..13aa72098 100644 --- a/core/src/avm2/globals/flash/net/url_loader.rs +++ b/core/src/avm2/globals/flash/net/url_loader.rs @@ -1,95 +1,15 @@ -//! `flash.net.URLLoader` builtin/prototype +//! `flash.net.URLLoader` native function definitions use crate::avm2::activation::Activation; -use crate::avm2::class::Class; -use crate::avm2::method::{Method, NativeMethodImpl}; -use crate::avm2::names::{Multiname, Namespace, QName}; +use crate::avm2::names::QName; use crate::avm2::object::TObject; use crate::avm2::value::Value; use crate::avm2::{Error, Object}; use crate::backend::navigator::Request; use crate::loader::DataFormat; -use gc_arena::{GcCell, MutationContext}; -/// Implements `flash.net.URLLoader`'s class constructor. -fn class_init<'gc>( - _activation: &mut Activation<'_, 'gc, '_>, - _this: Option>, - _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined) -} - -/// Implements `flash.net.URLLoader`'s instance constructor. -fn instance_init<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - this: Option>, - args: &[Value<'gc>], -) -> Result, Error> { - if let Some(mut this) = this { - activation.super_init(this, &[])?; - this.set_property( - &QName::dynamic_name("dataFormat").into(), - "text".into(), - activation, - )?; - this.set_property( - &QName::dynamic_name("data").into(), - Value::Undefined, - activation, - )?; - - if let Some(request) = args.get(0) { - if request != &Value::Null { - load(activation, Some(this), args)?; - } - } - } - Ok(Value::Undefined) -} - -fn bytes_loaded<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - this: Option>, - args: &[Value<'gc>], -) -> Result, Error> { - // For now, just use `bytes_total`. The `bytesLoaded` value - // should really update as the download progresses, instead - // of jumping at completion from 0 to the total length - log::warn!("URLLoader.bytesLoaded - not yet implemented"); - bytes_total(activation, this, args) -} - -fn bytes_total<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - this: Option>, - _args: &[Value<'gc>], -) -> Result, Error> { - if let Some(this) = this { - let data = this.get_property(&QName::dynamic_name("data").into(), activation)?; - - if let Value::Object(data) = data { - // `bytesTotal` should be 0 while the download is in progress - // (the `data` property is only set after the download is completed) - if let Some(array) = data.as_bytearray() { - return Ok(array.len().into()); - } else { - return Err(format!( - "Unexpected value for `data` property: {:?} {:?}", - data, - data.as_primitive() - ) - .into()); - } - } else if let Value::String(data) = data { - return Ok(data.len().into()); - } - return Ok(0.into()); - } - Ok(Value::Undefined) -} - -fn load<'gc>( +/// Native function definition for `URLLoader.load` +pub fn load<'gc>( activation: &mut Activation<'_, 'gc, '_>, this: Option>, args: &[Value<'gc>], @@ -133,44 +53,10 @@ fn spawn_fetch<'gc>( let future = activation.context.load_manager.load_data_into_url_loader( activation.context.player.clone(), loader_object, - // FIXME - get these from the `URLRequest` - Request::get(url.to_utf8_lossy().into_owned()), + // FIXME - set options from the `URLRequest` + Request::get(url.to_string()), data_format, ); activation.context.navigator.spawn_future(future); Ok(Value::Undefined) } - -pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { - let class = Class::new( - QName::new(Namespace::package("flash.net"), "URLLoader"), - Some(QName::new(Namespace::package("flash.events"), "EventDispatcher").into()), - Method::from_builtin(instance_init, "", mc), - Method::from_builtin(class_init, "", mc), - mc, - ); - - let mut write = class.write(mc); - - const PUBLIC_INSTANCE_PROPERTIES: &[( - &str, - Option, - Option, - )] = &[ - ("bytesLoaded", Some(bytes_loaded), None), - ("bytesTotal", Some(bytes_total), None), - ]; - write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES); - - const PUBLIC_INSTANCE_SLOTS: &[(&str, &str, &str)] = &[("dataFormat", "", "String")]; - write.define_public_slot_instance_traits(PUBLIC_INSTANCE_SLOTS); - - // This can't be a constant, due to the inability to declare `Multiname<'gc>` - let public_instance_slots_any = &[("data", Multiname::any())]; - write.define_public_slot_instance_traits_type_multiname(public_instance_slots_any); - - const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = &[("load", load)]; - write.define_public_builtin_instance_methods(mc, PUBLIC_INSTANCE_METHODS); - - class -} diff --git a/core/src/avm2/globals/flash/net/url_request.rs b/core/src/avm2/globals/flash/net/url_request.rs deleted file mode 100644 index 8c7690eae..000000000 --- a/core/src/avm2/globals/flash/net/url_request.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! `flash.net.URLRequest` 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::TObject; -use crate::avm2::value::Value; -use crate::avm2::{Error, Object}; -use gc_arena::{GcCell, MutationContext}; - -/// Implements `flash.net.URLRequest`'s class constructor. -pub fn class_init<'gc>( - _activation: &mut Activation<'_, 'gc, '_>, - _this: Option>, - _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined) -} - -/// Implements `flash.net.URLRequest`'s instance constructor. -pub fn instance_init<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - this: Option>, - args: &[Value<'gc>], -) -> Result, Error> { - if let Some(mut this) = this { - if let Some(url) = args.get(0) { - this.set_property( - &QName::new(Namespace::public(), "url").into(), - *url, - activation, - )?; - } - } - Ok(Value::Undefined) -} - -pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { - let class = Class::new( - QName::new(Namespace::package("flash.net"), "URLRequest"), - Some(QName::new(Namespace::public(), "Object").into()), - Method::from_builtin(instance_init, "", mc), - Method::from_builtin(class_init, "", mc), - mc, - ); - - let mut write = class.write(mc); - write.set_attributes(ClassAttributes::FINAL | ClassAttributes::SEALED); - - // NOTE - when implementing properties (e.g. `contentType`, `data`, etc.) - // be sure to also check for them in `UrlLoader` - const PUBLIC_INSTANCE_SLOTS: &[(&str, &str, &str)] = &[("url", "", "String")]; - write.define_public_slot_instance_traits(PUBLIC_INSTANCE_SLOTS); - - class -} diff --git a/core/src/avm2/globals/flash/system/Security.as b/core/src/avm2/globals/flash/system/Security.as new file mode 100644 index 000000000..257613a38 --- /dev/null +++ b/core/src/avm2/globals/flash/system/Security.as @@ -0,0 +1,15 @@ +package flash.system { + public final class Security { + public static native function get sandboxType():String; + public static native function allowDomain(... domains):void; + public static native function allowInsecureDomain(... domains):void; + public static native function loadPolicyFile(url: String):void; + public static native function showSettings(panel: String = "default"):void; + + public static const APPLICATION:String = "application"; + public static const LOCAL_TRUSTED:String = "localTrusted"; + public static const LOCAL_WITH_FILE:String = "localWithFile"; + public static const LOCAL_WITH_NETWORK:String = "localWithNetwork"; + public static const REMOTE:String = "remote"; + } +} diff --git a/core/src/avm2/globals/flash/system/security.rs b/core/src/avm2/globals/flash/system/security.rs index 75ff8bb4f..5ae1f7447 100644 --- a/core/src/avm2/globals/flash/system/security.rs +++ b/core/src/avm2/globals/flash/system/security.rs @@ -1,32 +1,12 @@ -//! `flash.system.Security` class +//! `flash.system.Security` native methods use crate::avm2::activation::Activation; -use crate::avm2::class::{Class, ClassAttributes}; -use crate::avm2::method::{Method, NativeMethodImpl}; -use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; use crate::string::AvmString; -use gc_arena::{GcCell, MutationContext}; -fn instance_init<'gc>( - _activation: &mut Activation<'_, 'gc, '_>, - _this: Option>, - _args: &[Value<'gc>], -) -> Result, Error> { - Err("The Security class cannot be constructed.".into()) -} - -fn class_init<'gc>( - _activation: &mut Activation<'_, 'gc, '_>, - _this: Option>, - _args: &[Value<'gc>], -) -> Result, Error> { - Ok(Value::Undefined) -} - -fn sandbox_type<'gc>( +pub fn get_sandbox_type<'gc>( activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -35,7 +15,7 @@ fn sandbox_type<'gc>( return Ok(AvmString::new_utf8(activation.context.gc_context, sandbox_type).into()); } -fn allow_domain<'gc>( +pub fn allow_domain<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -44,7 +24,7 @@ fn allow_domain<'gc>( Ok(Value::Undefined) } -fn allow_insecure_domain<'gc>( +pub fn allow_insecure_domain<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -53,7 +33,7 @@ fn allow_insecure_domain<'gc>( Ok(Value::Undefined) } -fn load_policy_file<'gc>( +pub fn load_policy_file<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -62,7 +42,7 @@ fn load_policy_file<'gc>( Ok(Value::Undefined) } -fn show_settings<'gc>( +pub fn show_settings<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -70,40 +50,3 @@ fn show_settings<'gc>( log::warn!("Security.showSettings not implemented"); Ok(Value::Undefined) } - -pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { - let class = Class::new( - QName::new(Namespace::package("flash.system"), "Security"), - Some(QName::new(Namespace::public(), "Object").into()), - Method::from_builtin(instance_init, "", mc), - Method::from_builtin(class_init, "", mc), - mc, - ); - - let mut write = class.write(mc); - - write.set_attributes(ClassAttributes::SEALED); - - const PUBLIC_CLASS_TRAITS: &[(&str, Option, Option)] = - &[("sandboxType", Some(sandbox_type), None)]; - write.define_public_builtin_class_properties(mc, PUBLIC_CLASS_TRAITS); - - const PUBLIC_CLASS_METHODS: &[(&str, NativeMethodImpl)] = &[ - ("allowDomain", allow_domain), - ("allowInsecureDomain", allow_insecure_domain), - ("loadPolicyFile", load_policy_file), - ("showSettings", show_settings), - ]; - write.define_public_builtin_class_methods(mc, PUBLIC_CLASS_METHODS); - - const CONSTANTS: &[(&str, &str)] = &[ - ("APPLICATION", "application"), - ("LOCAL_TRUSTED", "localTrusted"), - ("LOCAL_WITH_FILE", "localWithFile"), - ("LOCAL_WITH_NETWORK", "localWithNetwork"), - ("REMOTE", "remote"), - ]; - write.define_public_constant_string_class_traits(CONSTANTS); - - class -} diff --git a/core/src/avm2/globals/flash/utils.as b/core/src/avm2/globals/flash/utils.as new file mode 100644 index 000000000..44677d641 --- /dev/null +++ b/core/src/avm2/globals/flash/utils.as @@ -0,0 +1,3 @@ +package flash.utils { + public native function getDefinitionByName(name:String):Object; +} diff --git a/core/src/avm2/globals/flash/utils.rs b/core/src/avm2/globals/flash/utils.rs index 1938ec8c3..19c3f9a69 100644 --- a/core/src/avm2/globals/flash/utils.rs +++ b/core/src/avm2/globals/flash/utils.rs @@ -83,7 +83,7 @@ pub fn get_qualified_super_class_name<'gc>( } } -/// Implements `flash.utils.getDefinitionByName` +/// Implements native method `flash.utils.getDefinitionByName` pub fn get_definition_by_name<'gc>( activation: &mut Activation<'_, 'gc, '_>, _this: Option>, diff --git a/core/src/avm2/globals/globals.as b/core/src/avm2/globals/globals.as index 351341d40..3eb608ab3 100644 --- a/core/src/avm2/globals/globals.as +++ b/core/src/avm2/globals/globals.as @@ -34,9 +34,12 @@ include "flash/geom/Matrix.as" include "flash/geom/Point.as" include "flash/geom/Rectangle.as" include "flash/net/SharedObjectFlushStatus.as" +include "flash/net/URLLoader.as" include "flash/net/URLLoaderDataFormat.as" +include "flash/net/URLRequest.as" include "flash/net/URLRequestHeader.as" include "flash/net/URLRequestMethod.as" +include "flash/system/Security.as" include "flash/text/AntiAliasType.as" include "flash/text/FontStyle.as" include "flash/text/FontType.as" @@ -49,5 +52,6 @@ include "flash/text/TextFieldType.as" include "flash/text/TextFormatAlign.as" include "flash/text/TextInteractionMode.as" include "flash/text/TextLineMetrics.as" +include "flash/utils.as" include "flash/utils/CompressionAlgorithm.as" include "flash/utils/Endian.as" diff --git a/core/src/avm2/globals/stubs.as b/core/src/avm2/globals/stubs.as index d84efbaa0..903f37082 100644 --- a/core/src/avm2/globals/stubs.as +++ b/core/src/avm2/globals/stubs.as @@ -6,6 +6,7 @@ include "Object.as" // List is ordered alphabetically. include "Array.as" include "Boolean.as" +include "flash/events/EventDispatcher.as" include "Number.as" include "String.as" include "int.as" diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index 242f7a857..ff01a3273 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -215,7 +215,7 @@ pub fn specialized_class_init<'gc>( &QName::dynamic_name(*pubname).into(), FunctionObject::from_function( activation, - Method::from_builtin(*func, pubname, activation.context.gc_context), + Method::from_builtin(*func, *pubname, activation.context.gc_context), scope, )? .into(), diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs index fbd50d96b..c0c0ef2d1 100644 --- a/core/src/avm2/method.rs +++ b/core/src/avm2/method.rs @@ -8,6 +8,7 @@ use crate::avm2::value::{abc_default_value, Value}; use crate::avm2::Error; use crate::string::AvmString; use gc_arena::{Collect, CollectionContext, Gc, MutationContext}; +use std::borrow::Cow; use std::fmt; use std::rc::Rc; use swf::avm2::types::{ @@ -140,7 +141,7 @@ impl<'gc> BytecodeMethod<'gc> { abc_method: Index, is_function: bool, activation: &mut Activation<'_, 'gc, '_>, - ) -> Result, Error> { + ) -> Result { let abc = txunit.abc(); let mut signature = Vec::new(); @@ -162,34 +163,28 @@ impl<'gc> BytecodeMethod<'gc> { for (index, method_body) in abc.method_bodies.iter().enumerate() { if method_body.method.0 == abc_method.0 { - return Ok(Gc::allocate( - activation.context.gc_context, - Self { - txunit, - abc: txunit.abc(), - abc_method: abc_method.0, - abc_method_body: Some(index as u32), - signature, - return_type, - is_function, - }, - )); + return Ok(Self { + txunit, + abc: txunit.abc(), + abc_method: abc_method.0, + abc_method_body: Some(index as u32), + signature, + return_type, + is_function, + }); } } } - Ok(Gc::allocate( - activation.context.gc_context, - Self { - txunit, - abc: txunit.abc(), - abc_method: abc_method.0, - abc_method_body: None, - signature, - return_type: Multiname::any(), - is_function, - }, - )) + Ok(Self { + txunit, + abc: txunit.abc(), + abc_method: abc_method.0, + abc_method_body: None, + signature, + return_type: Multiname::any(), + is_function, + }) } /// Get the underlying ABC file. @@ -276,7 +271,7 @@ pub struct NativeMethod<'gc> { pub method: NativeMethodImpl, /// The name of the method. - pub name: &'static str, + pub name: Cow<'static, str>, /// The parameter signature of the method. pub signature: Vec>, @@ -325,7 +320,7 @@ impl<'gc> Method<'gc> { /// Define a builtin method with a particular param configuration. pub fn from_builtin_and_params( method: NativeMethodImpl, - name: &'static str, + name: impl Into>, signature: Vec>, is_variadic: bool, mc: MutationContext<'gc, '_>, @@ -334,7 +329,7 @@ impl<'gc> Method<'gc> { mc, NativeMethod { method, - name, + name: name.into(), signature, is_variadic, }, @@ -344,14 +339,14 @@ impl<'gc> Method<'gc> { /// Define a builtin with no parameter constraints. pub fn from_builtin( method: NativeMethodImpl, - name: &'static str, + name: impl Into>, mc: MutationContext<'gc, '_>, ) -> Self { Self::Native(Gc::allocate( mc, NativeMethod { method, - name, + name: name.into(), signature: Vec::new(), is_variadic: true, }, diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index e2bd35560..780b2b502 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -14,6 +14,7 @@ use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; use crate::string::AvmString; use gc_arena::{Collect, Gc, GcCell, MutationContext}; +use std::borrow::Cow; use std::cell::Ref; use std::mem::drop; use std::rc::Rc; @@ -129,11 +130,29 @@ impl<'gc> TranslationUnit<'gc> { return Ok(method.clone()); } + let is_global = read.domain.is_avm2_global_domain(activation); drop(read); - let method: Result>, Error> = - BytecodeMethod::from_method_index(self, method_index, is_function, activation); - let method: Method<'gc> = method?.into(); + let bc_method = + BytecodeMethod::from_method_index(self, method_index, is_function, activation)?; + + // This closure lets us move out of 'bc_method.signature' and then return, + // allowing us to use 'bc_method' later on without a borrow-checker error. + let method = (|| { + if is_global { + if let Some(native) = activation.avm2().native_table[method_index.0 as usize] { + let variadic = bc_method.is_variadic(); + return Method::from_builtin_and_params( + native, + Cow::Owned(bc_method.method_name().to_string()), + bc_method.signature, + variadic, + activation.context.gc_context, + ); + } + } + Gc::allocate(activation.context.gc_context, bc_method).into() + })(); self.0.write(activation.context.gc_context).methods[method_index.0 as usize] = Some(method.clone());