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:
parent
41bfc028d2
commit
af4f181856
|
@ -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"
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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), &[])?;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -132,7 +132,7 @@ impl<'gc> Executable<'gc> {
|
|||
}
|
||||
|
||||
let arguments = activation.resolve_parameters(
|
||||
bm.method.name,
|
||||
&bm.method.name,
|
||||
arguments,
|
||||
&bm.method.signature,
|
||||
)?;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// This is a stub - the actual class is defined in `eventdispatcher.rs`
|
||||
package flash.events {
|
||||
public class EventDispatcher {
|
||||
}
|
||||
}
|
|
@ -3,4 +3,3 @@
|
|||
pub mod object_encoding;
|
||||
pub mod sharedobject;
|
||||
pub mod url_loader;
|
||||
pub mod url_request;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package flash.utils {
|
||||
public native function getDefinitionByName(name:String):Object;
|
||||
}
|
|
@ -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>>,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,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<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,
|
||||
},
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in New Issue