2022-06-15 19:00:17 +00:00
|
|
|
//! An internal Ruffle utility to build our playerglobal
|
|
|
|
//! `library.swf`
|
|
|
|
|
2022-08-14 23:34:45 +00:00
|
|
|
use convert_case::{Boundary, Case, Casing};
|
2022-06-16 02:11:14 +00:00
|
|
|
use proc_macro2::TokenStream;
|
|
|
|
use quote::quote;
|
2023-02-01 20:45:10 +00:00
|
|
|
use regex::RegexBuilder;
|
|
|
|
use std::ffi::OsStr;
|
|
|
|
use std::fs;
|
2022-06-15 19:00:17 +00:00
|
|
|
use std::fs::File;
|
2022-08-26 04:08:50 +00:00
|
|
|
use std::io::ErrorKind;
|
2022-06-16 02:11:14 +00:00
|
|
|
use std::io::Write;
|
|
|
|
use std::path::{Path, PathBuf};
|
2022-06-15 19:00:17 +00:00
|
|
|
use std::process::Command;
|
2022-06-16 02:11:14 +00:00
|
|
|
use std::str::FromStr;
|
|
|
|
use swf::avm2::types::*;
|
2022-07-21 06:11:46 +00:00
|
|
|
use swf::avm2::write::Writer;
|
2022-08-26 12:20:04 +00:00
|
|
|
use swf::{DoAbc, DoAbcFlag, Header, Tag};
|
2023-02-01 20:45:10 +00:00
|
|
|
use walkdir::WalkDir;
|
2022-06-15 19:00:17 +00:00
|
|
|
|
2022-07-21 06:11:46 +00:00
|
|
|
// The metadata name - all metadata in our .as files
|
|
|
|
// should be of the form `[Ruffle(key1 = value1, key2 = value2)]`
|
|
|
|
const RUFFLE_METADATA_NAME: &str = "Ruffle";
|
|
|
|
// Indicates that we should generate a reference to an instance allocator
|
|
|
|
// method (used as a metadata key with `Ruffle` metadata)
|
|
|
|
const METADATA_INSTANCE_ALLOCATOR: &str = "InstanceAllocator";
|
2022-11-23 23:30:47 +00:00
|
|
|
// Indicates that we should generate a reference to a native initializer
|
|
|
|
// method (used as a metadata key with `Ruffle` metadata)
|
|
|
|
const METADATA_NATIVE_INSTANCE_INIT: &str = "NativeInstanceInit";
|
2022-07-21 06:11:46 +00:00
|
|
|
|
2022-06-15 19:00:17 +00:00
|
|
|
/// 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,
|
2023-02-01 20:45:10 +00:00
|
|
|
with_stubs: bool,
|
2022-06-15 19:00:17 +00:00
|
|
|
) -> 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");
|
|
|
|
|
|
|
|
// This will create 'playerglobal.abc', 'playerglobal.cpp', and 'playerglobal.h'
|
|
|
|
// in `out_dir`
|
2022-08-26 04:08:50 +00:00
|
|
|
let status = Command::new("java")
|
2023-02-11 18:28:53 +00:00
|
|
|
.args([
|
2022-06-17 13:25:47 +00:00
|
|
|
"-classpath",
|
|
|
|
&asc_path.to_string_lossy(),
|
|
|
|
"macromedia.asc.embedding.ScriptCompiler",
|
|
|
|
"-optimize",
|
|
|
|
"-outdir",
|
|
|
|
&out_dir.to_string_lossy(),
|
|
|
|
"-out",
|
|
|
|
"playerglobal",
|
|
|
|
"-import",
|
|
|
|
&classes_dir.join("stubs.as").to_string_lossy(),
|
2022-08-26 08:27:05 +00:00
|
|
|
// From some reason this has to be passed as a separate argument.
|
|
|
|
&classes_dir.join("Toplevel.as").to_string_lossy(),
|
2022-06-17 13:25:47 +00:00
|
|
|
&classes_dir.join("globals.as").to_string_lossy(),
|
|
|
|
])
|
2022-08-26 04:08:50 +00:00
|
|
|
.status();
|
|
|
|
match status {
|
|
|
|
Ok(code) => {
|
|
|
|
if !code.success() {
|
2022-10-26 23:46:09 +00:00
|
|
|
return Err(format!("Compiling failed with code {code:?}").into());
|
2022-08-26 04:08:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
if err.kind() == ErrorKind::NotFound {
|
|
|
|
return Err("Java could not be found on your computer. Please install java, then try compiling again.".into());
|
|
|
|
}
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2022-06-15 19:00:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let playerglobal = out_dir.join("playerglobal");
|
2022-07-21 06:11:46 +00:00
|
|
|
let mut bytes = std::fs::read(playerglobal.with_extension("abc"))?;
|
2022-06-15 19:00:17 +00:00
|
|
|
|
|
|
|
// 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"))?;
|
|
|
|
|
2023-02-01 20:45:10 +00:00
|
|
|
if with_stubs {
|
|
|
|
collect_stubs(&classes_dir, &out_dir)?;
|
|
|
|
}
|
|
|
|
|
2022-07-21 06:11:46 +00:00
|
|
|
bytes = write_native_table(&bytes, &out_dir)?;
|
2022-06-16 02:11:14 +00:00
|
|
|
|
2022-08-26 12:20:04 +00:00
|
|
|
let tags = [Tag::DoAbc(DoAbc {
|
|
|
|
flags: DoAbcFlag::LAZY_INITIALIZE,
|
|
|
|
name: "".into(),
|
2022-06-15 19:00:17 +00:00
|
|
|
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(())
|
|
|
|
}
|
2022-06-16 02:11:14 +00:00
|
|
|
|
|
|
|
// 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 {
|
2022-10-26 23:46:09 +00:00
|
|
|
panic!("Unexpected Multiname {multiname:?}");
|
2022-06-16 02:11:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2022-10-26 23:46:09 +00:00
|
|
|
panic!("Unexpected Namespace {ns:?}");
|
2022-06-16 02:11:14 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-10-26 23:46:09 +00:00
|
|
|
panic!("Unexpected Multiname {multiname:?}");
|
2022-06-16 02:11:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-21 06:11:46 +00:00
|
|
|
fn flash_to_rust_path(path: &str) -> String {
|
|
|
|
// 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('.')
|
2022-08-14 23:34:45 +00:00
|
|
|
.map(|component| {
|
avm2: Partially implement Stage3D for wgpu backend
This PR implements core 'stage3D' APIs. We are now able
to render at least two demos from the Context3D docs - a simple
triangle render, and a rotating cube.
Implemented in this PR:
* Stage3D access and Context3D creation
* IndexBuffer3D and VertexBuffer3D creation, uploading, and usage
* Program3D uploading and usage (via `naga-agal`)
* Context3D: configureBackBuffer, clear, drawTriangles, and present
Not yet implemented:
* Any 'dispose()' methods
* Depth and stencil buffers
* Context3D texture apis
* Scissor rectangle
General implementation strategy:
A new `Object` variant is added for each of the Stage3D objects
(VertexBuffer3D, Program3D, etc). This stores a handle to the
parent `Context3D`, and (depending on the object) a handle
to the underlying native resource, via `Rc<dyn
SomeRenderBackendTrait>`).
Calling methods on Context3D does not usually result in an immediate
call to a `wgpu` method. Instead, we queue up commands in our
`Context3D` instance, and execute them all on a call to `present`.
This avoids some nasty wgpu lifetime issues, and is very similar
to the approah we use for normal rendering.
The actual rendering happens on a `Texture`, with dimensions
determined by `createBackBuffer`. During 'Stage' rendering,
we render all of these Stage3D textures *behind* the normal
stage (but in front of the overall stage background color).
2022-09-18 20:50:21 +00:00
|
|
|
// Special-case this so that it matches the Flash namespace
|
|
|
|
if component == "display3D" {
|
|
|
|
return component.to_string();
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut without_boundaries = vec![Boundary::DigitUpper];
|
|
|
|
// Special case for classes ending in '3D' - we want to ave something like
|
|
|
|
// 'vertex_buffer_3d' instead of 'vertex_buffer3d'
|
|
|
|
if !component.ends_with("3D") {
|
|
|
|
// Do not split on a letter followed by a digit, so e.g. `atan2` won't become `atan_2`.
|
2023-02-11 18:28:53 +00:00
|
|
|
without_boundaries.extend([Boundary::UpperDigit, Boundary::LowerDigit]);
|
avm2: Partially implement Stage3D for wgpu backend
This PR implements core 'stage3D' APIs. We are now able
to render at least two demos from the Context3D docs - a simple
triangle render, and a rotating cube.
Implemented in this PR:
* Stage3D access and Context3D creation
* IndexBuffer3D and VertexBuffer3D creation, uploading, and usage
* Program3D uploading and usage (via `naga-agal`)
* Context3D: configureBackBuffer, clear, drawTriangles, and present
Not yet implemented:
* Any 'dispose()' methods
* Depth and stencil buffers
* Context3D texture apis
* Scissor rectangle
General implementation strategy:
A new `Object` variant is added for each of the Stage3D objects
(VertexBuffer3D, Program3D, etc). This stores a handle to the
parent `Context3D`, and (depending on the object) a handle
to the underlying native resource, via `Rc<dyn
SomeRenderBackendTrait>`).
Calling methods on Context3D does not usually result in an immediate
call to a `wgpu` method. Instead, we queue up commands in our
`Context3D` instance, and execute them all on a call to `present`.
This avoids some nasty wgpu lifetime issues, and is very similar
to the approah we use for normal rendering.
The actual rendering happens on a `Texture`, with dimensions
determined by `createBackBuffer`. During 'Stage' rendering,
we render all of these Stage3D textures *behind* the normal
stage (but in front of the overall stage background color).
2022-09-18 20:50:21 +00:00
|
|
|
}
|
|
|
|
|
2022-08-14 23:34:45 +00:00
|
|
|
component
|
|
|
|
.from_case(Case::Camel)
|
avm2: Partially implement Stage3D for wgpu backend
This PR implements core 'stage3D' APIs. We are now able
to render at least two demos from the Context3D docs - a simple
triangle render, and a rotating cube.
Implemented in this PR:
* Stage3D access and Context3D creation
* IndexBuffer3D and VertexBuffer3D creation, uploading, and usage
* Program3D uploading and usage (via `naga-agal`)
* Context3D: configureBackBuffer, clear, drawTriangles, and present
Not yet implemented:
* Any 'dispose()' methods
* Depth and stencil buffers
* Context3D texture apis
* Scissor rectangle
General implementation strategy:
A new `Object` variant is added for each of the Stage3D objects
(VertexBuffer3D, Program3D, etc). This stores a handle to the
parent `Context3D`, and (depending on the object) a handle
to the underlying native resource, via `Rc<dyn
SomeRenderBackendTrait>`).
Calling methods on Context3D does not usually result in an immediate
call to a `wgpu` method. Instead, we queue up commands in our
`Context3D` instance, and execute them all on a call to `present`.
This avoids some nasty wgpu lifetime issues, and is very similar
to the approah we use for normal rendering.
The actual rendering happens on a `Texture`, with dimensions
determined by `createBackBuffer`. During 'Stage' rendering,
we render all of these Stage3D textures *behind* the normal
stage (but in front of the overall stage background color).
2022-09-18 20:50:21 +00:00
|
|
|
.without_boundaries(&without_boundaries)
|
2022-08-14 23:34:45 +00:00
|
|
|
.to_case(Case::Snake)
|
|
|
|
})
|
2022-07-21 06:11:46 +00:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
// Form a Rust path from the snake-case components
|
|
|
|
components.join("::")
|
|
|
|
}
|
|
|
|
|
2022-09-25 00:14:40 +00:00
|
|
|
fn rust_method_name_and_path(
|
2022-07-21 06:11:46 +00:00
|
|
|
abc: &AbcFile,
|
|
|
|
trait_: &Trait,
|
|
|
|
parent: Option<Index<Multiname>>,
|
|
|
|
prefix: &str,
|
|
|
|
suffix: &str,
|
|
|
|
) -> TokenStream {
|
|
|
|
let mut path = "crate::avm2::globals::".to_string();
|
|
|
|
|
|
|
|
let trait_name = &abc.constant_pool.multinames[trait_.name.0 as usize - 1];
|
2022-09-25 00:14:40 +00:00
|
|
|
// We build up the Flash method path (e.g. flash.utils::getDefinitionByName)
|
|
|
|
// and store it in the table. This gets used by Ruffle to display proper
|
|
|
|
// stack traces involving native methods.
|
|
|
|
let mut flash_method_path = String::new();
|
2022-07-21 06:11:46 +00:00
|
|
|
|
|
|
|
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];
|
2023-02-11 18:28:53 +00:00
|
|
|
let ns = flash_to_rust_path(resolve_multiname_ns(abc, multiname));
|
2022-08-14 23:01:19 +00:00
|
|
|
if !ns.is_empty() {
|
|
|
|
path += &ns;
|
|
|
|
path += "::";
|
2022-09-25 00:14:40 +00:00
|
|
|
|
|
|
|
flash_method_path += &ns;
|
|
|
|
flash_method_path += "::";
|
2022-08-14 23:01:19 +00:00
|
|
|
}
|
2023-02-11 18:28:53 +00:00
|
|
|
let name = resolve_multiname_name(abc, multiname);
|
2022-09-25 00:14:40 +00:00
|
|
|
path += &flash_to_rust_path(name);
|
2022-07-21 06:11:46 +00:00
|
|
|
path += "::";
|
2022-09-25 00:14:40 +00:00
|
|
|
|
2023-02-11 18:28:53 +00:00
|
|
|
flash_method_path += name;
|
2022-09-25 00:14:40 +00:00
|
|
|
flash_method_path += "::";
|
2022-07-21 06:11:46 +00:00
|
|
|
} 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"
|
2023-02-11 18:28:53 +00:00
|
|
|
let name = resolve_multiname_ns(abc, trait_name);
|
2022-09-25 00:14:40 +00:00
|
|
|
let ns = &flash_to_rust_path(name);
|
2023-02-11 18:28:53 +00:00
|
|
|
path += ns;
|
2022-09-25 00:14:40 +00:00
|
|
|
flash_method_path += name;
|
2022-08-28 18:39:27 +00:00
|
|
|
if !ns.is_empty() {
|
|
|
|
path += "::";
|
2022-09-25 00:14:40 +00:00
|
|
|
flash_method_path += "::";
|
2022-08-28 18:39:27 +00:00
|
|
|
}
|
2022-07-21 06:11:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Append the trait name - this corresponds to the actual method
|
|
|
|
// name (e.g. `getDefinitionByName`)
|
|
|
|
path += prefix;
|
|
|
|
|
2023-02-11 18:28:53 +00:00
|
|
|
let name = resolve_multiname_name(abc, trait_name);
|
2022-09-25 00:14:40 +00:00
|
|
|
|
|
|
|
path += &flash_to_rust_path(name);
|
|
|
|
flash_method_path += name;
|
2022-07-21 06:11:46 +00:00
|
|
|
|
|
|
|
path += suffix;
|
|
|
|
|
|
|
|
// 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();
|
2022-09-25 00:14:40 +00:00
|
|
|
quote! { Some((#flash_method_path, #path_tokens)) }
|
2022-07-21 06:11:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn strip_metadata(abc: &mut AbcFile) {
|
|
|
|
abc.metadata.clear();
|
|
|
|
for instance in &mut abc.instances {
|
|
|
|
for trait_ in &mut instance.traits {
|
|
|
|
trait_.metadata.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for class in &mut abc.classes {
|
|
|
|
for trait_ in &mut class.traits {
|
|
|
|
trait_.metadata.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for script in &mut abc.scripts {
|
|
|
|
for trait_ in &mut script.traits {
|
|
|
|
trait_.metadata.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for body in &mut abc.method_bodies {
|
|
|
|
for trait_ in &mut body.traits {
|
|
|
|
trait_.metadata.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-16 02:11:14 +00:00
|
|
|
/// 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.
|
2022-07-21 06:11:46 +00:00
|
|
|
///
|
|
|
|
/// Returns a modified version of 'data' that should be saved to disk
|
|
|
|
/// in our generated SWF
|
|
|
|
fn write_native_table(data: &[u8], out_dir: &Path) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
2022-06-16 02:11:14 +00:00
|
|
|
let mut reader = swf::avm2::read::Reader::new(data);
|
2022-07-21 06:11:46 +00:00
|
|
|
let mut abc = reader.read()?;
|
2022-06-16 02:11:14 +00:00
|
|
|
|
|
|
|
let none_tokens = quote! { None };
|
2022-07-21 06:11:46 +00:00
|
|
|
let mut rust_paths = vec![none_tokens.clone(); abc.methods.len()];
|
2022-11-23 23:30:47 +00:00
|
|
|
let mut rust_instance_allocators = vec![none_tokens.clone(); abc.classes.len()];
|
|
|
|
let mut rust_native_instance_initializers = vec![none_tokens; abc.classes.len()];
|
2022-06-16 02:11:14 +00:00
|
|
|
|
|
|
|
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 { .. } => {
|
2022-10-26 23:46:09 +00:00
|
|
|
panic!("TraitKind::Function is not supported: {trait_:?}")
|
2022-06-16 02:11:14 +00:00
|
|
|
}
|
|
|
|
_ => 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_",
|
|
|
|
_ => "",
|
|
|
|
};
|
|
|
|
|
2022-07-21 06:11:46 +00:00
|
|
|
rust_paths[method_id.0 as usize] =
|
2022-09-25 00:14:40 +00:00
|
|
|
rust_method_name_and_path(&abc, trait_, parent, method_prefix, "");
|
2022-07-21 06:11:46 +00:00
|
|
|
};
|
2022-06-16 02:11:14 +00:00
|
|
|
|
2022-07-21 06:11:46 +00:00
|
|
|
// Look for `[Ruffle(InstanceAllocator)]` metadata - if present,
|
|
|
|
// generate a reference to an allocator function in the native instance
|
|
|
|
// allocators table.
|
|
|
|
let mut check_instance_allocator = |trait_: &Trait| {
|
2022-09-17 03:39:14 +00:00
|
|
|
let class_id = if let TraitKind::Class { class, .. } = trait_.kind {
|
|
|
|
class.0
|
2022-07-21 06:11:46 +00:00
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
};
|
2022-06-16 02:11:14 +00:00
|
|
|
|
2022-09-17 03:39:14 +00:00
|
|
|
let class_name_idx = abc.instances[class_id as usize].name.0;
|
2022-07-21 06:11:46 +00:00
|
|
|
let class_name = resolve_multiname_name(
|
|
|
|
&abc,
|
|
|
|
&abc.constant_pool.multinames[class_name_idx as usize - 1],
|
|
|
|
);
|
2022-06-16 02:11:14 +00:00
|
|
|
|
2022-11-23 23:30:47 +00:00
|
|
|
let instance_allocator_method_name =
|
|
|
|
"::".to_string() + &flash_to_rust_path(class_name) + "_allocator";
|
|
|
|
let native_instance_init_method_name = "::native_instance_init".to_string();
|
2022-07-21 06:11:46 +00:00
|
|
|
for metadata_idx in &trait_.metadata {
|
|
|
|
let metadata = &abc.metadata[metadata_idx.0 as usize];
|
|
|
|
let name = &abc.constant_pool.strings[metadata.name.0 as usize - 1];
|
|
|
|
match name.as_str() {
|
|
|
|
RUFFLE_METADATA_NAME => {}
|
2022-10-26 23:46:09 +00:00
|
|
|
_ => panic!("Unexpected class metadata {name:?}"),
|
2022-07-21 06:11:46 +00:00
|
|
|
}
|
2022-06-16 02:11:14 +00:00
|
|
|
|
2022-07-21 06:11:46 +00:00
|
|
|
for item in &metadata.items {
|
|
|
|
let key = if item.key.0 != 0 {
|
|
|
|
Some(abc.constant_pool.strings[item.key.0 as usize - 1].as_str())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
let value = &abc.constant_pool.strings[item.value.0 as usize - 1];
|
|
|
|
match (key, value.as_str()) {
|
|
|
|
// Match `[Ruffle(InstanceAllocator)]`
|
|
|
|
(None, METADATA_INSTANCE_ALLOCATOR) => {
|
|
|
|
// This results in a path of the form
|
|
|
|
// `crate::avm2::globals::<path::to::class>::<class_allocator>`
|
2022-11-23 23:30:47 +00:00
|
|
|
rust_instance_allocators[class_id as usize] = rust_method_name_and_path(
|
|
|
|
&abc,
|
|
|
|
trait_,
|
|
|
|
None,
|
|
|
|
"",
|
|
|
|
&instance_allocator_method_name,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
(None, METADATA_NATIVE_INSTANCE_INIT) => {
|
|
|
|
rust_native_instance_initializers[class_id as usize] =
|
|
|
|
rust_method_name_and_path(
|
|
|
|
&abc,
|
|
|
|
trait_,
|
|
|
|
None,
|
|
|
|
"",
|
|
|
|
&native_instance_init_method_name,
|
|
|
|
)
|
2022-07-21 06:11:46 +00:00
|
|
|
}
|
2022-10-26 23:46:09 +00:00
|
|
|
_ => panic!("Unexpected metadata pair ({key:?}, {value})"),
|
2022-07-21 06:11:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-16 02:11:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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);
|
2022-07-21 06:11:46 +00:00
|
|
|
check_instance_allocator(trait_);
|
2022-06-16 02:11:14 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-21 06:11:46 +00:00
|
|
|
// Finally, generate the actual code.
|
2022-06-16 02:11:14 +00:00
|
|
|
let make_native_table = quote! {
|
2022-07-21 06:11:46 +00:00
|
|
|
// This is a Rust array -
|
2022-09-25 00:14:40 +00:00
|
|
|
// the entry at index `i` is the method name and Rust function pointer for the native
|
2022-07-21 06:11:46 +00:00
|
|
|
// 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.
|
2022-09-25 00:14:40 +00:00
|
|
|
pub const NATIVE_METHOD_TABLE: &[Option<(&'static str, crate::avm2::method::NativeMethodImpl)>] = &[
|
2022-06-16 02:11:14 +00:00
|
|
|
#(#rust_paths,)*
|
|
|
|
];
|
2022-07-21 06:11:46 +00:00
|
|
|
|
|
|
|
// This is very similar to `NATIVE_METHOD_TABLE`, but we have one entry per
|
|
|
|
// class, rather than per method. When an entry is `Some(fn_ptr)`, we use
|
|
|
|
// `fn_ptr` as the instance allocator for the corresponding class when we
|
|
|
|
// load it into Ruffle.
|
2022-09-25 00:14:40 +00:00
|
|
|
pub const NATIVE_INSTANCE_ALLOCATOR_TABLE: &[Option<(&'static str, crate::avm2::class::AllocatorFn)>] = &[
|
2022-07-21 06:11:46 +00:00
|
|
|
#(#rust_instance_allocators,)*
|
|
|
|
];
|
2022-11-23 23:30:47 +00:00
|
|
|
|
|
|
|
// This is very similar to `NATIVE_METHOD_TABLE`, but we have one entry per
|
|
|
|
// class, rather than per method. When an entry is `Some(fn_ptr)`, we use
|
|
|
|
// `fn_ptr` as the native initializer for the corresponding class when we
|
|
|
|
// load it into Ruffle.
|
|
|
|
pub const NATIVE_INSTANCE_INIT_TABLE: &[Option<(&'static str, crate::avm2::method::NativeMethodImpl)>] = &[
|
|
|
|
#(#rust_native_instance_initializers,)*
|
|
|
|
];
|
2022-06-16 02:11:14 +00:00
|
|
|
}
|
|
|
|
.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())?;
|
|
|
|
|
2022-07-21 06:11:46 +00:00
|
|
|
// Ruffle doesn't need metadata items at runtime, so strip
|
|
|
|
// them out to save space
|
|
|
|
strip_metadata(&mut abc);
|
|
|
|
|
|
|
|
let mut out_bytes = Vec::new();
|
|
|
|
let mut writer = Writer::new(&mut out_bytes);
|
|
|
|
writer.write(abc).expect("Failed to write modified ABC");
|
|
|
|
|
|
|
|
Ok(out_bytes)
|
2022-06-16 02:11:14 +00:00
|
|
|
}
|
2023-02-01 20:45:10 +00:00
|
|
|
|
|
|
|
fn collect_stubs(root: &Path, out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
let pattern = RegexBuilder::new(
|
|
|
|
r#"
|
2023-02-07 17:32:31 +00:00
|
|
|
\b (?P<type> stub_method | stub_getter | stub_setter | stub_constructor) \s*
|
2023-02-01 20:45:10 +00:00
|
|
|
\( \s*
|
|
|
|
"(?P<class> .+)" \s*
|
|
|
|
, \s*
|
|
|
|
"(?P<property> .+)" \s*
|
|
|
|
(?:|
|
|
|
|
, \s*
|
|
|
|
"(?P<specifics> .+)" \s*
|
|
|
|
)
|
|
|
|
\) \s*
|
|
|
|
;
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
.ignore_whitespace(true)
|
|
|
|
.build()?;
|
|
|
|
|
|
|
|
let mut stubs = Vec::new();
|
|
|
|
|
|
|
|
for entry in WalkDir::new(root)
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|f| f.ok())
|
|
|
|
.filter(|f| f.path().extension() == Some(OsStr::new("as")))
|
|
|
|
{
|
|
|
|
let contents = fs::read_to_string(entry.path())?;
|
|
|
|
for entry in pattern.captures_iter(&contents) {
|
|
|
|
let class = &entry["class"];
|
2023-02-07 17:32:31 +00:00
|
|
|
let property = entry.name("property").map(|m| m.as_str());
|
2023-02-01 20:45:10 +00:00
|
|
|
let specifics = entry.name("specifics").map(|m| m.as_str());
|
|
|
|
|
2023-02-07 17:32:31 +00:00
|
|
|
match (&entry["type"], property, specifics) {
|
|
|
|
("stub_method", Some(property), Some(specifics)) => stubs.push(quote! {
|
2023-02-01 20:45:10 +00:00
|
|
|
crate::stub::Stub::Avm2Method {
|
|
|
|
class: Cow::Borrowed(#class),
|
|
|
|
method: Cow::Borrowed(#property),
|
|
|
|
specifics: Cow::Borrowed(#specifics)
|
|
|
|
}
|
|
|
|
}),
|
2023-02-07 17:32:31 +00:00
|
|
|
("stub_method", Some(property), None) => stubs.push(quote! {
|
2023-02-01 20:45:10 +00:00
|
|
|
crate::stub::Stub::Avm2Method {
|
|
|
|
class: Cow::Borrowed(#class),
|
|
|
|
method: Cow::Borrowed(#property),
|
|
|
|
specifics: None
|
|
|
|
}
|
|
|
|
}),
|
2023-02-07 17:32:31 +00:00
|
|
|
("stub_getter", Some(property), _) => stubs.push(quote! {
|
2023-02-01 20:45:10 +00:00
|
|
|
crate::stub::Stub::Avm2Getter {
|
|
|
|
class: Cow::Borrowed(#class),
|
|
|
|
property: Cow::Borrowed(#property)
|
|
|
|
}
|
|
|
|
}),
|
2023-02-07 17:32:31 +00:00
|
|
|
("stub_setter", Some(property), _) => stubs.push(quote! {
|
2023-02-01 20:45:10 +00:00
|
|
|
crate::stub::Stub::Avm2Setter {
|
|
|
|
class: Cow::Borrowed(#class),
|
|
|
|
property: Cow::Borrowed(#property)
|
|
|
|
}
|
|
|
|
}),
|
2023-02-07 17:32:31 +00:00
|
|
|
("stub_constructor", Some(property), _) => stubs.push(quote! {
|
|
|
|
// Property is actually specifics here
|
|
|
|
crate::stub::Stub::Avm2Constructor {
|
|
|
|
class: Cow::Borrowed(#class),
|
|
|
|
specifics: Some(Cow::Borrowed(#property))
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
("stub_constructor", None, _) => stubs.push(quote! {
|
|
|
|
crate::stub::Stub::Avm2Constructor {
|
|
|
|
class: Cow::Borrowed(#class),
|
|
|
|
specifics: None
|
|
|
|
}
|
|
|
|
}),
|
2023-02-01 20:45:10 +00:00
|
|
|
_ => panic!("Unsupported stub type {}", &entry["type"]),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let stub_block = quote! {
|
|
|
|
#[cfg(feature = "known_stubs")]
|
|
|
|
use std::borrow::Cow;
|
|
|
|
|
|
|
|
#[cfg(feature = "known_stubs")]
|
|
|
|
pub static AS_DEFINED_STUBS: &[crate::stub::Stub] = &[
|
|
|
|
#(#stubs,)*
|
|
|
|
];
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut as_stub_file = File::create(out_dir.join("actionscript_stubs.rs"))?;
|
|
|
|
as_stub_file.write_all(stub_block.to_string().as_bytes())?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|