avm2: Allow implementing classes in ActionScript in custom `playerglobal`
This PR adds support for building a custom `playerglobal.swf`, which can be used to implement builtin Flash classes in ActionScript. This file is embedded into Ruffle using `include_bytes!`, and loaded during initialization. As an example, the `Point` class is reimplemented in ActionScript, and `flash.text.AntiAliasType` is added. The ActionScript compilation process is performed by `core/build.rs`. See that file, along with `core/src/avm2/globals/README.md`, for more details.
This commit is contained in:
parent
b8cdd59877
commit
f629a91e53
|
@ -251,6 +251,14 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7"
|
||||
|
||||
[[package]]
|
||||
name = "build_playerglobal"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"swf",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.9.1"
|
||||
|
@ -2992,6 +3000,7 @@ dependencies = [
|
|||
"approx",
|
||||
"bitflags",
|
||||
"bitstream-io",
|
||||
"build_playerglobal",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"dasp",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
members = [
|
||||
"core",
|
||||
"core/macros",
|
||||
"core/build_playerglobal",
|
||||
"desktop",
|
||||
"swf",
|
||||
"web",
|
||||
|
|
|
@ -39,6 +39,8 @@ For more detailed instructions, see our [wiki page](https://github.com/ruffle-rs
|
|||
|
||||
[Follow the official guide](https://www.rust-lang.org/tools/install) to install Rust for your platform.
|
||||
|
||||
You must also have Java installed, and available on your PATH as `java`.
|
||||
|
||||
### Desktop
|
||||
|
||||
If you are building for a Linux platform, make sure that the GTK 3 development packages are
|
||||
|
|
|
@ -72,3 +72,6 @@ lzma = ["lzma-rs", "swf/lzma"]
|
|||
wasm-bindgen = [ "instant/wasm-bindgen" ]
|
||||
avm_debug = []
|
||||
deterministic = []
|
||||
|
||||
[build-dependencies]
|
||||
build_playerglobal = { path = "build_playerglobal" }
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
fn main() {
|
||||
build_playerglobal::build_playerglobal("../".into(), std::env::var("OUT_DIR").unwrap().into())
|
||||
.expect("Failed to build playerglobal");
|
||||
|
||||
// This is overly conservative - it will cause us to rebuild playerglobal.swf
|
||||
// if *any* files in this directory change, not just .as files.
|
||||
// However, this script is fast to run, so it shouldn't matter in practice.
|
||||
// If Cargo ever adds glob support to 'rerun-if-changed', we should use it.
|
||||
println!("cargo:rerun-if-changed=src/avm2/globals/");
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "build_playerglobal"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
swf = { path = "../../swf" }
|
||||
walkdir = "2.3.2"
|
Binary file not shown.
|
@ -0,0 +1,91 @@
|
|||
//! An internal Ruffle utility to build our playerglobal
|
||||
//! `library.swf`
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use swf::DoAbc;
|
||||
use swf::Header;
|
||||
use swf::SwfStr;
|
||||
use swf::Tag;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
/// If successful, returns a list of paths that were used. If this is run
|
||||
/// from a build script, these paths should be printed with
|
||||
/// cargo:rerun-if-changed
|
||||
pub fn build_playerglobal(
|
||||
repo_root: PathBuf,
|
||||
out_dir: PathBuf,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let classes_dir = repo_root.join("core/src/avm2/globals/");
|
||||
let asc_path = repo_root.join("core/build_playerglobal/asc.jar");
|
||||
|
||||
let out_path = out_dir.join("playerglobal.swf");
|
||||
|
||||
// These classes are currently stubs - they're referenced by
|
||||
// other classes that we need to compile, but the real definition
|
||||
// is in Ruffle itself (in Rust code).
|
||||
// As a result, we don't emit them into the final SWF (but we do
|
||||
// provide them to asc.jar with '-import' to link against).
|
||||
let stub_classes: HashSet<_> = ["Object", "Number", "Boolean", "String"].into();
|
||||
|
||||
// This will create 'playerglobal.abc', 'playerglobal.cpp', and 'playerglobal.h'
|
||||
// in `out_dir`
|
||||
let mut cmd = Command::new("java");
|
||||
cmd.args(&[
|
||||
"-classpath",
|
||||
&asc_path.to_string_lossy(),
|
||||
"macromedia.asc.embedding.ScriptCompiler",
|
||||
"-outdir",
|
||||
&out_dir.to_string_lossy(),
|
||||
"-out",
|
||||
"playerglobal",
|
||||
]);
|
||||
|
||||
for entry in WalkDir::new(&classes_dir) {
|
||||
let entry = entry?;
|
||||
if entry.path().extension().and_then(|e| e.to_str()) != Some("as") {
|
||||
continue;
|
||||
}
|
||||
let class = entry.into_path();
|
||||
let class_name: String = class
|
||||
.strip_prefix(&classes_dir)?
|
||||
.with_extension("")
|
||||
.iter()
|
||||
.map(|c| c.to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
|
||||
if stub_classes.contains(class_name.as_str()) {
|
||||
cmd.arg("-import");
|
||||
}
|
||||
cmd.arg(class);
|
||||
}
|
||||
|
||||
println!("Compiling: {:?}", cmd);
|
||||
let code = cmd.status()?;
|
||||
if !code.success() {
|
||||
return Err(format!("Compiling failed with code {:?}", code).into());
|
||||
}
|
||||
|
||||
let playerglobal = out_dir.join("playerglobal");
|
||||
let bytes = std::fs::read(playerglobal.with_extension("abc"))?;
|
||||
|
||||
// Cleanup the temporary files written out by 'asc.jar'
|
||||
std::fs::remove_file(playerglobal.with_extension("abc"))?;
|
||||
std::fs::remove_file(playerglobal.with_extension("cpp"))?;
|
||||
std::fs::remove_file(playerglobal.with_extension("h"))?;
|
||||
|
||||
let tags = vec![Tag::DoAbc(DoAbc {
|
||||
name: SwfStr::from_utf8_str(""),
|
||||
is_lazy_initialize: true,
|
||||
data: &bytes,
|
||||
})];
|
||||
|
||||
let header = Header::default_with_swf_version(19);
|
||||
let out_file = File::create(out_path).unwrap();
|
||||
swf::write_swf(&header, &tags, out_file)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
//! Manually builds `playerglobal.swf` without building the `core` crate.
|
||||
//! This binary is invoked as:
|
||||
//! `cargo run --package=build_playerglobal <repo_root> <out_dir>`
|
||||
//! where `<repo_root>` is the location of the Ruffle repository,
|
||||
//! and `out_dir` is the directory where `playerglobal.swf` should
|
||||
//! be written
|
||||
|
||||
fn main() {
|
||||
build_playerglobal::build_playerglobal(
|
||||
std::env::args().nth(1).unwrap().into(),
|
||||
std::env::args().nth(2).unwrap().into(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
|
@ -6,11 +6,12 @@ use crate::avm2::object::EventObject;
|
|||
use crate::avm2::script::{Script, TranslationUnit};
|
||||
use crate::context::UpdateContext;
|
||||
use crate::string::AvmString;
|
||||
use crate::tag_utils::SwfSlice;
|
||||
use crate::tag_utils::{SwfSlice, SwfStream};
|
||||
use fnv::FnvHashMap;
|
||||
use gc_arena::{Collect, MutationContext};
|
||||
use std::rc::Rc;
|
||||
use swf::avm2::read::Reader;
|
||||
use swf::extensions::ReadSwfExt;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! avm_debug {
|
||||
|
@ -261,6 +262,37 @@ impl<'gc> Avm2<'gc> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_abc_from_do_abc(
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
swf: &SwfSlice,
|
||||
domain: Domain<'gc>,
|
||||
reader: &mut SwfStream<'_>,
|
||||
tag_len: usize,
|
||||
) -> Result<(), Error> {
|
||||
let start = reader.as_slice();
|
||||
// Queue the actions.
|
||||
// TODO: The tag reader parses the entire ABC file, instead of just
|
||||
// giving us a `SwfSlice` for later parsing, so we have to replcate the
|
||||
// *entire* parsing code here. This sucks.
|
||||
let flags = reader.read_u32()?;
|
||||
let name = reader.read_str()?.to_string_lossy(reader.encoding());
|
||||
let is_lazy_initialize = flags & 1 != 0;
|
||||
let num_read = reader.pos(start);
|
||||
|
||||
// The rest of the tag is an ABC file so we can take our SwfSlice now.
|
||||
let slice = swf
|
||||
.resize_to_reader(reader, tag_len - num_read)
|
||||
.ok_or_else(|| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Invalid source or tag length when running init action",
|
||||
)
|
||||
})?;
|
||||
|
||||
Avm2::load_abc(slice, &name, is_lazy_initialize, context, domain)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load an ABC file embedded in a `SwfSlice`.
|
||||
///
|
||||
/// The `SwfSlice` must resolve to the contents of an ABC file.
|
||||
|
|
|
@ -9,9 +9,13 @@ use crate::avm2::object::{
|
|||
use crate::avm2::scope::{Scope, ScopeChain};
|
||||
use crate::avm2::script::Script;
|
||||
use crate::avm2::value::Value;
|
||||
use crate::avm2::Avm2;
|
||||
use crate::avm2::Error;
|
||||
use crate::string::AvmString;
|
||||
use crate::tag_utils::{self, SwfMovie, SwfSlice, SwfStream};
|
||||
use gc_arena::{Collect, GcCell, MutationContext};
|
||||
use std::sync::Arc;
|
||||
use swf::TagCode;
|
||||
|
||||
mod array;
|
||||
mod boolean;
|
||||
|
@ -522,6 +526,8 @@ pub fn load_player_globals<'gc>(
|
|||
// After this point, it is safe to initialize any other classes.
|
||||
// Make sure to initialize superclasses *before* their subclasses!
|
||||
|
||||
load_playerglobal(activation, domain)?;
|
||||
|
||||
avm2_system_class!(string, activation, string::create_class(mc), script);
|
||||
avm2_system_class!(boolean, activation, boolean::create_class(mc), script);
|
||||
avm2_system_class!(number, activation, number::create_class(mc), script);
|
||||
|
@ -900,12 +906,6 @@ pub fn load_player_globals<'gc>(
|
|||
|
||||
// package `flash.geom`
|
||||
class(activation, flash::geom::matrix::create_class(mc), script)?;
|
||||
avm2_system_class!(
|
||||
point,
|
||||
activation,
|
||||
flash::geom::point::create_class(mc),
|
||||
script
|
||||
);
|
||||
avm2_system_class!(
|
||||
rectangle,
|
||||
activation,
|
||||
|
@ -1034,3 +1034,54 @@ pub fn load_player_globals<'gc>(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This file is built by 'core/build_playerglobal/'
|
||||
/// See that tool, and 'core/src/avm2/globals/README.md', for more details
|
||||
const PLAYERGLOBAL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/playerglobal.swf"));
|
||||
|
||||
/// 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> {
|
||||
let movie = Arc::new(SwfMovie::from_data(PLAYERGLOBAL, None, None)?);
|
||||
|
||||
let slice = SwfSlice::from(movie);
|
||||
|
||||
let mut reader = slice.read_from(0);
|
||||
|
||||
let tag_callback = |reader: &mut SwfStream<'_>, tag_code, tag_len| {
|
||||
if tag_code == TagCode::DoAbc {
|
||||
Avm2::load_abc_from_do_abc(&mut activation.context, &slice, domain, reader, tag_len)?;
|
||||
} else if tag_code != TagCode::End {
|
||||
panic!(
|
||||
"playerglobal should only contain `DoAbc` tag - found tag {:?}",
|
||||
tag_code
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::End);
|
||||
macro_rules! avm2_system_classes_playerglobal {
|
||||
($activation:expr, $script:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => {
|
||||
$(
|
||||
let qname = QName::new(Namespace::package($package), $class_name);
|
||||
let class_object = activation.resolve_class(&qname.into())?;
|
||||
let sc = $activation.avm2().system_classes.as_mut().unwrap();
|
||||
sc.$field = class_object;
|
||||
|
||||
let sp = $activation.avm2().system_prototypes.as_mut().unwrap();
|
||||
sp.$field = class_object.prototype();
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
// This acts the same way as 'avm2_system_class', but for classes
|
||||
// declared in 'playerglobal'. Classes are declared as ("package", "class", field_name),
|
||||
// and are stored in 'avm2().system_classes' and 'avm2().system_prototypes'
|
||||
avm2_system_classes_playerglobal!(activation, script, [("flash.geom", "Point", point),]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// This is a stub - the actual class is defined in `boolean.rs`
|
||||
package {
|
||||
public class Boolean {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// This is a stub - the actual class is defined in `number.rs`
|
||||
package {
|
||||
public class Number {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// This is a stub - the actual class is defined in `object.rs`
|
||||
package {
|
||||
public dynamic class Object {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
# Ruffle AVM2 globals
|
||||
|
||||
This directory contains implementations of Flash global
|
||||
definitions (e.g. `Object`, `flash.geom.Point`, `flash.utils.getDefinitionByName`)
|
||||
|
||||
WARNING: Do *not* implement classes by copying their (decompiled) ActionScript
|
||||
from the Adobe Flash `playerglobal.swf`. This would violate copyright by making
|
||||
Ruffle re-distribute Adobe's code (and will not even work in general, since
|
||||
Adobe's `playerglobal.swf` uses native methods that Ruffle doesn't implement).
|
||||
|
||||
Currently, globals are implemented in one of two ways:
|
||||
1) As pure-Rust code in files like `system.rs`. These are normal Rust
|
||||
modules, and are used from `core/src/avm2/global.rs`
|
||||
2) As ActionScript class definitions like `flash/geom/Point.as`.
|
||||
These files are automatically compiled into a `playerglobal.swf`
|
||||
file at build time, which is included into the final Ruffle binary
|
||||
and loaded during player initialization.
|
||||
|
||||
In many cases, defining a class in ActionScript results in
|
||||
code that's much simpler and more readable than if were
|
||||
defined in Rust.
|
||||
|
||||
Flash's `playerglobal.swc` (specifically, its `library.swf`)
|
||||
cannot be used as a drop-in replacement for our `playerglobal.swf`.
|
||||
In addition to potential copyright issues around redistributing Flash's `playerglobal.swc`,
|
||||
many of its classes rely on specific 'native' methods being provided
|
||||
by the Flash VM, which Ruffle does not implement.
|
||||
|
||||
## Compiling
|
||||
|
||||
Java must be installed for the build process to complete.
|
||||
|
||||
ActionScript classes are processed by the `core/build\_playerglobal`
|
||||
|
||||
The tool first uses 'asc.jar'
|
||||
from the Flex SDK to compile these ActionScript files into
|
||||
ABC (bytecode). This tool is entirely self-contained, so we can
|
||||
include in our repository without requiring the entire Flex SDK
|
||||
to be installed.
|
||||
|
||||
The produced ABC files are then combined into the final
|
||||
`playerglobal.swf` (which is written out into the build directory,
|
||||
and not checked in to Git).
|
||||
|
||||
The `core/build\_playerglobal` tool is automatically run by `core`'s build script
|
||||
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 Ruffle initialization
|
||||
(e.g. 'Object', 'Function', 'Class') cannot currently
|
||||
be implemented in 'playerglobal', since Ruffle initializes them in a special
|
||||
way. However, virtually all classes in the 'flash' package are initialized
|
||||
in a 'normal' way, and are eligible for implementation in 'playerglobal'
|
|
@ -0,0 +1,5 @@
|
|||
// This is a stub - the actual class is defined in `string.rs`
|
||||
package {
|
||||
public class String {
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
//! `flash.geom` namespace
|
||||
|
||||
pub mod matrix;
|
||||
pub mod point;
|
||||
pub mod rectangle;
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package flash.geom {
|
||||
public class Point {
|
||||
public var x:Number;
|
||||
public var y:Number;
|
||||
|
||||
public function Point(x:Number = 0, y:Number = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public function get length():Number {
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y);
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return "(x=" + this.x + ", y=" + this.y + ")";
|
||||
}
|
||||
|
||||
public function add(v:Point):Point {
|
||||
return new Point(this.x + v.x, this.y + v.y);
|
||||
}
|
||||
|
||||
public function subtract(v:Point):Point {
|
||||
return new Point(this.x - v.x, this.y - v.y);
|
||||
}
|
||||
|
||||
public function clone():Point {
|
||||
return new Point(this.x, this.y);
|
||||
}
|
||||
|
||||
public function copyFrom(sourcePoint:Point):void {
|
||||
this.x = sourcePoint.x;
|
||||
this.y = sourcePoint.y;
|
||||
}
|
||||
|
||||
public function equals(toCompare:Point):Boolean {
|
||||
return this.x == toCompare.x && this.y == toCompare.y;
|
||||
}
|
||||
|
||||
public function normalize(thickness:Number):void {
|
||||
var len:Number = this.length;
|
||||
if (len > 0) {
|
||||
var inv_d:Number = thickness / len;
|
||||
this.x *= inv_d;
|
||||
this.y *= inv_d;
|
||||
}
|
||||
}
|
||||
|
||||
public function offset(dx:Number, dy:Number):void {
|
||||
this.x += dx;
|
||||
this.y += dy;
|
||||
}
|
||||
|
||||
public function setTo(xa:Number, ya:Number):void {
|
||||
this.x = xa;
|
||||
this.y = ya;
|
||||
}
|
||||
|
||||
public static function distance(pt1:Point, pt2:Point):Number {
|
||||
return pt2.subtract(pt1).length;
|
||||
}
|
||||
|
||||
public static function interpolate(pt1:Point, pt2:Point, f:Number):Point {
|
||||
return new Point(pt2.x - (pt2.x - pt1.x) * f, pt2.y - (pt2.y - pt1.y) * f);
|
||||
}
|
||||
|
||||
public static function polar(len:Number, angle:Number):Point {
|
||||
return new Point(len* Math.cos(angle), len * Math.sin(angle));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,379 +0,0 @@
|
|||
//! `flash.geom.Point` builtin/prototype
|
||||
|
||||
use crate::avm2::class::{Class, ClassAttributes};
|
||||
use crate::avm2::method::{Method, NativeMethodImpl};
|
||||
use crate::avm2::{Activation, Error, Namespace, Object, QName, TObject, Value};
|
||||
use crate::string::AvmString;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
|
||||
pub fn create_point<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
coords: (f64, f64),
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
let point_class = activation.context.avm2.classes().point;
|
||||
|
||||
let args = [Value::Number(coords.0), Value::Number(coords.1)];
|
||||
let new_point = point_class.construct(activation, &args)?;
|
||||
|
||||
Ok(new_point.into())
|
||||
}
|
||||
|
||||
/// Implements `flash.geom.Point`'s instance constructor.
|
||||
pub fn instance_init<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
let _ = set_to(activation, this, args)?;
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
fn coords<'gc>(
|
||||
this: &mut Object<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
) -> Result<(f64, f64), Error> {
|
||||
let x = this
|
||||
.get_property(&QName::new(Namespace::public(), "x").into(), activation)?
|
||||
.coerce_to_number(activation)?;
|
||||
let y = this
|
||||
.get_property(&QName::new(Namespace::public(), "y").into(), activation)?
|
||||
.coerce_to_number(activation)?;
|
||||
Ok((x, y))
|
||||
}
|
||||
|
||||
fn set_coords<'gc>(
|
||||
this: &mut Object<'gc>,
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
value: (f64, f64),
|
||||
) -> Result<(), Error> {
|
||||
this.set_property(
|
||||
&QName::new(Namespace::public(), "x").into(),
|
||||
value.0.into(),
|
||||
activation,
|
||||
)?;
|
||||
this.set_property(
|
||||
&QName::new(Namespace::public(), "y").into(),
|
||||
value.1.into(),
|
||||
activation,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Implements `flash.geom.Point`'s class initializer.
|
||||
pub fn class_init<'gc>(
|
||||
_activation: &mut Activation<'_, 'gc, '_>,
|
||||
_this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements the `length` property
|
||||
pub fn length<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(mut this) = this {
|
||||
let (x, y) = coords(&mut this, activation)?;
|
||||
|
||||
return Ok((x * x + y * y).sqrt().into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `add`
|
||||
pub fn add<'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(other) = args.get(0) {
|
||||
let mut other_obj = other.coerce_to_object(activation)?;
|
||||
let (our_x, our_y) = coords(&mut this, activation)?;
|
||||
let (their_x, their_y) = coords(&mut other_obj, activation)?;
|
||||
|
||||
return create_point(activation, (our_x + their_x, our_y + their_y));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `clone`
|
||||
pub fn clone<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(mut this) = this {
|
||||
let (our_x, our_y) = coords(&mut this, activation)?;
|
||||
|
||||
return create_point(activation, (our_x, our_y));
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `copyFrom`
|
||||
pub fn copy_from<'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(other) = args.get(0) {
|
||||
let mut other_obj = other.coerce_to_object(activation)?;
|
||||
let (their_x, their_y) = coords(&mut other_obj, activation)?;
|
||||
|
||||
set_coords(&mut this, activation, (their_x, their_y))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `distance`
|
||||
pub fn distance<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
_this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(first) = args.get(0) {
|
||||
let mut first_object = first.coerce_to_object(activation)?;
|
||||
if let Some(second) = args.get(1) {
|
||||
let mut second_obj = second.coerce_to_object(activation)?;
|
||||
let (our_x, our_y) = coords(&mut first_object, activation)?;
|
||||
let (their_x, their_y) = coords(&mut second_obj, activation)?;
|
||||
|
||||
return Ok(((our_x - their_x).powf(2.0) + (our_y - their_y).powf(2.0))
|
||||
.sqrt()
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `equals`
|
||||
#[allow(clippy::float_cmp)]
|
||||
pub fn equals<'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(other) = args.get(0) {
|
||||
let mut other_obj = other.coerce_to_object(activation)?;
|
||||
|
||||
let (our_x, our_y) = coords(&mut this, activation)?;
|
||||
let (their_x, their_y) = coords(&mut other_obj, activation)?;
|
||||
|
||||
return Ok((our_x == their_x && our_y == their_y).into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `interpolate`
|
||||
pub fn interpolate<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
_this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if args.len() < 3 {
|
||||
return create_point(activation, (f64::NAN, f64::NAN));
|
||||
}
|
||||
|
||||
let (a_x, a_y) = coords(
|
||||
&mut args.get(0).unwrap().coerce_to_object(activation)?,
|
||||
activation,
|
||||
)?;
|
||||
let (b_x, b_y) = coords(
|
||||
&mut args.get(1).unwrap().coerce_to_object(activation)?,
|
||||
activation,
|
||||
)?;
|
||||
let f = args.get(2).unwrap().coerce_to_number(activation)?;
|
||||
|
||||
let result = (b_x - (b_x - a_x) * f, b_y - (b_y - a_y) * f);
|
||||
create_point(activation, result)
|
||||
}
|
||||
|
||||
/// Implements `normalize`
|
||||
pub fn normalize<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(mut this) = this {
|
||||
let thickness = args
|
||||
.get(0)
|
||||
.unwrap_or(&0.into())
|
||||
.coerce_to_number(activation)?;
|
||||
|
||||
let length = length(activation, Some(this), args)?.coerce_to_number(activation)?;
|
||||
|
||||
if length > 0.0 {
|
||||
let inv_d = thickness / length;
|
||||
|
||||
let (old_x, old_y) = coords(&mut this, activation)?;
|
||||
set_coords(&mut this, activation, (old_x * inv_d, old_y * inv_d))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `offset`
|
||||
pub fn offset<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(mut this) = this {
|
||||
let (x, y) = coords(&mut this, activation)?;
|
||||
|
||||
let dx = args
|
||||
.get(0)
|
||||
.unwrap_or(&0.into())
|
||||
.coerce_to_number(activation)?;
|
||||
let dy = args
|
||||
.get(1)
|
||||
.unwrap_or(&0.into())
|
||||
.coerce_to_number(activation)?;
|
||||
|
||||
set_coords(&mut this, activation, (x + dx, y + dy))?;
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `polar`
|
||||
pub fn polar<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
_this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
let length = args
|
||||
.get(0)
|
||||
.unwrap_or(&Value::Undefined)
|
||||
.coerce_to_number(activation)?;
|
||||
let angle = args
|
||||
.get(1)
|
||||
.unwrap_or(&Value::Undefined)
|
||||
.coerce_to_number(activation)?;
|
||||
|
||||
create_point(activation, (length * angle.cos(), length * angle.sin()))
|
||||
}
|
||||
|
||||
/// Implements `setTo`
|
||||
pub fn set_to<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(mut this) = this {
|
||||
let x = args
|
||||
.get(0)
|
||||
.unwrap_or(&0.into())
|
||||
.coerce_to_number(activation)?;
|
||||
let y = args
|
||||
.get(1)
|
||||
.unwrap_or(&0.into())
|
||||
.coerce_to_number(activation)?;
|
||||
|
||||
set_coords(&mut this, activation, (x, y))?;
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `subtract`
|
||||
pub fn subtract<'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(other) = args.get(0) {
|
||||
let mut other_obj = other.coerce_to_object(activation)?;
|
||||
let (our_x, our_y) = coords(&mut this, activation)?;
|
||||
let (their_x, their_y) = coords(&mut other_obj, activation)?;
|
||||
|
||||
return create_point(activation, (our_x - their_x, our_y - their_y));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Implements `toString`
|
||||
pub fn to_string<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Some(this) = this {
|
||||
let x = this
|
||||
.get_property(&QName::new(Namespace::public(), "x").into(), activation)?
|
||||
.coerce_to_string(activation)?;
|
||||
let y = this
|
||||
.get_property(&QName::new(Namespace::public(), "y").into(), activation)?
|
||||
.coerce_to_string(activation)?;
|
||||
return Ok(AvmString::new_utf8(
|
||||
activation.context.gc_context,
|
||||
format!("(x={}, y={})", x, y),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Construct `Point`'s class.
|
||||
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
||||
let class = Class::new(
|
||||
QName::new(Namespace::package("flash.geom"), "Point"),
|
||||
Some(QName::new(Namespace::public(), "Object").into()),
|
||||
Method::from_builtin(instance_init, "<Point instance initializer>", mc),
|
||||
Method::from_builtin(class_init, "<Point class initializer>", mc),
|
||||
mc,
|
||||
);
|
||||
|
||||
let mut write = class.write(mc);
|
||||
write.set_attributes(ClassAttributes::SEALED);
|
||||
|
||||
const PUBLIC_INSTANCE_PROPERTIES: &[(
|
||||
&str,
|
||||
Option<NativeMethodImpl>,
|
||||
Option<NativeMethodImpl>,
|
||||
)] = &[("length", Some(length), None)];
|
||||
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
|
||||
|
||||
const PUBLIC_INSTANCE_NUMBER_SLOTS: &[(&str, Option<f64>)] = &[("x", None), ("y", None)];
|
||||
write.define_public_slot_number_instance_traits(PUBLIC_INSTANCE_NUMBER_SLOTS);
|
||||
|
||||
const PUBLIC_CLASS_METHODS: &[(&str, NativeMethodImpl)] = &[
|
||||
("distance", distance),
|
||||
("interpolate", interpolate),
|
||||
("polar", polar),
|
||||
];
|
||||
write.define_public_builtin_class_methods(mc, PUBLIC_CLASS_METHODS);
|
||||
|
||||
const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = &[
|
||||
("add", add),
|
||||
("clone", clone),
|
||||
("copyFrom", copy_from),
|
||||
("equals", equals),
|
||||
("normalize", normalize),
|
||||
("offset", offset),
|
||||
("setTo", set_to),
|
||||
("subtract", subtract),
|
||||
("toString", to_string),
|
||||
];
|
||||
write.define_public_builtin_instance_methods(mc, PUBLIC_INSTANCE_METHODS);
|
||||
class
|
||||
}
|
|
@ -1,12 +1,23 @@
|
|||
//! `flash.geom.Rectangle` builtin/prototype
|
||||
|
||||
use crate::avm2::class::{Class, ClassAttributes};
|
||||
use crate::avm2::globals::flash::geom::point::create_point;
|
||||
use crate::avm2::method::{Method, NativeMethodImpl};
|
||||
use crate::avm2::{Activation, Error, Namespace, Object, QName, TObject, Value};
|
||||
use crate::string::AvmString;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
|
||||
pub fn create_point<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
coords: (f64, f64),
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
let point_class = activation.context.avm2.classes().point;
|
||||
|
||||
let args = [Value::Number(coords.0), Value::Number(coords.1)];
|
||||
let new_point = point_class.construct(activation, &args)?;
|
||||
|
||||
Ok(new_point.into())
|
||||
}
|
||||
|
||||
pub fn create_rectangle<'gc>(
|
||||
activation: &mut Activation<'_, 'gc, '_>,
|
||||
properties: (f64, f64, f64, f64),
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package flash.text {
|
||||
public final class AntiAliasType {
|
||||
public static const ADVANCED:String = "advanced";
|
||||
public static const NORMAL:String = "normal";
|
||||
}
|
||||
}
|
|
@ -502,32 +502,15 @@ impl<'gc> MovieClip<'gc> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let start = reader.as_slice();
|
||||
// Queue the actions.
|
||||
// TODO: The tag reader parses the entire ABC file, instead of just
|
||||
// giving us a `SwfSlice` for later parsing, so we have to replcate the
|
||||
// *entire* parsing code here. This sucks.
|
||||
let flags = reader.read_u32()?;
|
||||
let name = reader.read_str()?.to_string_lossy(reader.encoding());
|
||||
let is_lazy_initialize = flags & 1 != 0;
|
||||
let domain = context.library.library_for_movie_mut(movie).avm2_domain();
|
||||
let num_read = reader.pos(start);
|
||||
|
||||
// The rest of the tag is an ABC file so we can take our SwfSlice now.
|
||||
let slice = self
|
||||
.0
|
||||
.read()
|
||||
.static_data
|
||||
.swf
|
||||
.resize_to_reader(reader, tag_len - num_read)
|
||||
.ok_or_else(|| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Invalid source or tag length when running init action",
|
||||
)
|
||||
})?;
|
||||
|
||||
if let Err(e) = Avm2::load_abc(slice, &name, is_lazy_initialize, context, domain) {
|
||||
if let Err(e) = Avm2::load_abc_from_do_abc(
|
||||
context,
|
||||
&self.0.read().static_data.swf,
|
||||
domain,
|
||||
reader,
|
||||
tag_len,
|
||||
) {
|
||||
log::warn!("Error loading ABC file: {}", e);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue