avm2: Implement support for native methods in `playerglobal`

This commit adds support for marking methods as `native`
in ActionScript classes defined in playerglobal. The
`build_playerglobal` now checks for native methods, and
generates Rust code linking them to a corresponding Rust
function definition in the codebase.

To test this functionality, I've reimplemented several
functions as native methods (and moved related code to
pure ActionScript).
This commit is contained in:
Aaron Hill 2022-06-15 21:11:14 -05:00
parent 41bfc028d2
commit af4f181856
24 changed files with 369 additions and 302 deletions

9
Cargo.lock generated
View File

@ -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"

View File

@ -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" }

View File

@ -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<dyn std::error::Error>> {
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<Index<Multiname>>| {
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::<Vec<_>>();
// 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<crate::avm2::method::NativeMethodImpl>] = &[
#(#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(())
}

View File

@ -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<SystemClasses<'gc>>,
#[collect(require_static)]
native_table: &'static [Option<NativeMethodImpl>],
/// 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), &[])?;
}

View File

@ -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

View File

@ -132,7 +132,7 @@ impl<'gc> Executable<'gc> {
}
let arguments = activation.resolve_parameters(
bm.method.name,
&bm.method.name,
arguments,
&bm.method.signature,
)?;

View File

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

View File

@ -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

View File

@ -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),

View File

@ -0,0 +1,5 @@
// This is a stub - the actual class is defined in `eventdispatcher.rs`
package flash.events {
public class EventDispatcher {
}
}

View File

@ -3,4 +3,3 @@
pub mod object_encoding;
pub mod sharedobject;
pub mod url_loader;
pub mod url_request;

View File

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

View File

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

View File

@ -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<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
/// Implements `flash.net.URLLoader`'s instance constructor.
fn instance_init<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, 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<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, 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<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, 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<Object<'gc>>,
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, "<URLLoader instance initializer>", mc),
Method::from_builtin(class_init, "<URLLoader class initializer>", mc),
mc,
);
let mut write = class.write(mc);
const PUBLIC_INSTANCE_PROPERTIES: &[(
&str,
Option<NativeMethodImpl>,
Option<NativeMethodImpl>,
)] = &[
("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
}

View File

@ -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<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
/// Implements `flash.net.URLRequest`'s instance constructor.
pub fn instance_init<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, 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, "<URLRequest instance initializer>", mc),
Method::from_builtin(class_init, "<URLRequest class initializer>", 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
}

View File

@ -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";
}
}

View File

@ -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<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Err("The Security class cannot be constructed.".into())
}
fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
fn sandbox_type<'gc>(
pub fn get_sandbox_type<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_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<Object<'gc>>,
_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<Object<'gc>>,
_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<Object<'gc>>,
_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<Object<'gc>>,
_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, "<Security instance initializer>", mc),
Method::from_builtin(class_init, "<Security class initializer>", mc),
mc,
);
let mut write = class.write(mc);
write.set_attributes(ClassAttributes::SEALED);
const PUBLIC_CLASS_TRAITS: &[(&str, Option<NativeMethodImpl>, Option<NativeMethodImpl>)] =
&[("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
}

View File

@ -0,0 +1,3 @@
package flash.utils {
public native function getDefinitionByName(name:String):Object;
}

View File

@ -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<Object<'gc>>,

View File

@ -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"

View File

@ -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"

View File

@ -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(),

View File

@ -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<AbcMethod>,
is_function: bool,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Gc<'gc, Self>, Error> {
) -> Result<Self, Error> {
let abc = txunit.abc();
let mut signature = Vec::new();
@ -162,9 +163,7 @@ 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 {
return Ok(Self {
txunit,
abc: txunit.abc(),
abc_method: abc_method.0,
@ -172,15 +171,12 @@ impl<'gc> BytecodeMethod<'gc> {
signature,
return_type,
is_function,
},
));
});
}
}
}
Ok(Gc::allocate(
activation.context.gc_context,
Self {
Ok(Self {
txunit,
abc: txunit.abc(),
abc_method: abc_method.0,
@ -188,8 +184,7 @@ impl<'gc> BytecodeMethod<'gc> {
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<ParamConfig<'gc>>,
@ -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<Cow<'static, str>>,
signature: Vec<ParamConfig<'gc>>,
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<Cow<'static, str>>,
mc: MutationContext<'gc, '_>,
) -> Self {
Self::Native(Gc::allocate(
mc,
NativeMethod {
method,
name,
name: name.into(),
signature: Vec::new(),
is_variadic: true,
},

View File

@ -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<Gc<'gc, BytecodeMethod<'gc>>, 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());