core: Added ability to mark stubs inside actionscript
This commit is contained in:
parent
dff558170e
commit
bdbf54ecd4
|
@ -307,9 +307,11 @@ dependencies = [
|
|||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde-xml-rs",
|
||||
"swf",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
fn main() {
|
||||
build_playerglobal::build_playerglobal("../".into(), std::env::var("OUT_DIR").unwrap().into())
|
||||
.expect("Failed to build playerglobal");
|
||||
build_playerglobal::build_playerglobal(
|
||||
"../".into(),
|
||||
std::env::var("OUT_DIR").unwrap().into(),
|
||||
cfg!(feature = "known_stubs"),
|
||||
)
|
||||
.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.
|
||||
|
|
|
@ -16,3 +16,5 @@ clap = {version = "4.1.4", features = ["derive"]}
|
|||
serde = {version = "1.0.152", features = ["derive"]}
|
||||
serde-xml-rs = "0.6.0"
|
||||
colored = "2.0.0"
|
||||
regex = "1.7.1"
|
||||
walkdir = "2.3.2"
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
use convert_case::{Boundary, Case, Casing};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use regex::RegexBuilder;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::Write;
|
||||
|
@ -13,6 +16,7 @@ use std::str::FromStr;
|
|||
use swf::avm2::types::*;
|
||||
use swf::avm2::write::Writer;
|
||||
use swf::{DoAbc, DoAbcFlag, Header, Tag};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
// The metadata name - all metadata in our .as files
|
||||
// should be of the form `[Ruffle(key1 = value1, key2 = value2)]`
|
||||
|
@ -30,6 +34,7 @@ const METADATA_NATIVE_INSTANCE_INIT: &str = "NativeInstanceInit";
|
|||
pub fn build_playerglobal(
|
||||
repo_root: PathBuf,
|
||||
out_dir: PathBuf,
|
||||
with_stubs: bool,
|
||||
) -> 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");
|
||||
|
@ -77,6 +82,10 @@ pub fn build_playerglobal(
|
|||
std::fs::remove_file(playerglobal.with_extension("cpp"))?;
|
||||
std::fs::remove_file(playerglobal.with_extension("h"))?;
|
||||
|
||||
if with_stubs {
|
||||
collect_stubs(&classes_dir, &out_dir)?;
|
||||
}
|
||||
|
||||
bytes = write_native_table(&bytes, &out_dir)?;
|
||||
|
||||
let tags = [Tag::DoAbc(DoAbc {
|
||||
|
@ -443,3 +452,83 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<Vec<u8>, Box<dyn st
|
|||
|
||||
Ok(out_bytes)
|
||||
}
|
||||
|
||||
fn collect_stubs(root: &Path, out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let pattern = RegexBuilder::new(
|
||||
r#"
|
||||
\b (?P<type> stub_method | stub_getter | stub_setter) \s*
|
||||
\( \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"];
|
||||
let property = &entry["property"];
|
||||
let specifics = entry.name("specifics").map(|m| m.as_str());
|
||||
|
||||
match (&entry["type"], specifics) {
|
||||
("stub_method", Some(specifics)) => stubs.push(quote! {
|
||||
crate::stub::Stub::Avm2Method {
|
||||
class: Cow::Borrowed(#class),
|
||||
method: Cow::Borrowed(#property),
|
||||
specifics: Cow::Borrowed(#specifics)
|
||||
}
|
||||
}),
|
||||
("stub_method", None) => stubs.push(quote! {
|
||||
crate::stub::Stub::Avm2Method {
|
||||
class: Cow::Borrowed(#class),
|
||||
method: Cow::Borrowed(#property),
|
||||
specifics: None
|
||||
}
|
||||
}),
|
||||
("stub_getter", _) => stubs.push(quote! {
|
||||
crate::stub::Stub::Avm2Getter {
|
||||
class: Cow::Borrowed(#class),
|
||||
property: Cow::Borrowed(#property)
|
||||
}
|
||||
}),
|
||||
("stub_setter", _) => stubs.push(quote! {
|
||||
crate::stub::Stub::Avm2Setter {
|
||||
class: Cow::Borrowed(#class),
|
||||
property: Cow::Borrowed(#property)
|
||||
}
|
||||
}),
|
||||
_ => 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(())
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ fn main() {
|
|||
let args = cli::Cli::parse();
|
||||
match args.command {
|
||||
Commands::Compile { out_dir } => {
|
||||
build_playerglobal::build_playerglobal(repo_root, out_dir.into()).unwrap();
|
||||
build_playerglobal::build_playerglobal(repo_root, out_dir.into(), false).unwrap();
|
||||
}
|
||||
Commands::Lint => {
|
||||
let tmp = root.join("tmp");
|
||||
|
|
|
@ -420,6 +420,27 @@ pub fn load_player_globals<'gc>(
|
|||
toplevel::log_warn,
|
||||
script,
|
||||
)?;
|
||||
function(
|
||||
activation,
|
||||
"__ruffle__",
|
||||
"stub_method",
|
||||
toplevel::stub_method,
|
||||
script,
|
||||
)?;
|
||||
function(
|
||||
activation,
|
||||
"__ruffle__",
|
||||
"stub_getter",
|
||||
toplevel::stub_getter,
|
||||
script,
|
||||
)?;
|
||||
function(
|
||||
activation,
|
||||
"__ruffle__",
|
||||
"stub_setter",
|
||||
toplevel::stub_setter,
|
||||
script,
|
||||
)?;
|
||||
function(activation, "", "isFinite", toplevel::is_finite, script)?;
|
||||
function(activation, "", "isNaN", toplevel::is_nan, script)?;
|
||||
function(activation, "", "parseInt", toplevel::parse_int, script)?;
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
package __ruffle__ {
|
||||
public native function log_warn(...arguments);
|
||||
|
||||
public native function stub_method(class_name, method);
|
||||
|
||||
public native function stub_getter(class_name, method);
|
||||
|
||||
public native function stub_setter(class_name, method);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ use crate::avm2::object::Object;
|
|||
use crate::avm2::value::Value;
|
||||
use crate::avm2::Error;
|
||||
use crate::string::{AvmString, WStr, WString};
|
||||
use crate::stub::Stub;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub fn trace<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
|
@ -54,6 +56,89 @@ pub fn log_warn<'gc>(
|
|||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
pub fn stub_method<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
_this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
match args {
|
||||
[class, method] => {
|
||||
let class = class.coerce_to_string(activation)?;
|
||||
let method = method.coerce_to_string(activation)?;
|
||||
activation
|
||||
.context
|
||||
.stub_tracker
|
||||
.encounter(&Stub::Avm2Method {
|
||||
class: Cow::Owned(class.to_utf8_lossy().to_string()),
|
||||
method: Cow::Owned(method.to_utf8_lossy().to_string()),
|
||||
specifics: None,
|
||||
});
|
||||
}
|
||||
[class, method, specifics] => {
|
||||
let class = class.coerce_to_string(activation)?;
|
||||
let method = method.coerce_to_string(activation)?;
|
||||
let specifics = specifics.coerce_to_string(activation)?;
|
||||
activation
|
||||
.context
|
||||
.stub_tracker
|
||||
.encounter(&Stub::Avm2Method {
|
||||
class: Cow::Owned(class.to_utf8_lossy().to_string()),
|
||||
method: Cow::Owned(method.to_utf8_lossy().to_string()),
|
||||
specifics: Some(Cow::Owned(specifics.to_utf8_lossy().to_string())),
|
||||
});
|
||||
}
|
||||
_ => tracing::warn!("(__ruffle__.stub_method called with wrong args)"),
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
pub fn stub_getter<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
_this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
match args {
|
||||
[class, property] => {
|
||||
let class = class.coerce_to_string(activation)?;
|
||||
let property = property.coerce_to_string(activation)?;
|
||||
activation
|
||||
.context
|
||||
.stub_tracker
|
||||
.encounter(&Stub::Avm2Getter {
|
||||
class: Cow::Owned(class.to_utf8_lossy().to_string()),
|
||||
property: Cow::Owned(property.to_utf8_lossy().to_string()),
|
||||
});
|
||||
}
|
||||
_ => tracing::warn!("(__ruffle__.stub_getter called with wrong args)"),
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
pub fn stub_setter<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
_this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
match args {
|
||||
[class, property] => {
|
||||
let class = class.coerce_to_string(activation)?;
|
||||
let property = property.coerce_to_string(activation)?;
|
||||
activation
|
||||
.context
|
||||
.stub_tracker
|
||||
.encounter(&Stub::Avm2Setter {
|
||||
class: Cow::Owned(class.to_utf8_lossy().to_string()),
|
||||
property: Cow::Owned(property.to_utf8_lossy().to_string()),
|
||||
});
|
||||
}
|
||||
_ => tracing::warn!("(__ruffle__.stub_setter called with wrong args)"),
|
||||
}
|
||||
|
||||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
pub fn is_finite<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
_this: Option<Object<'gc>>,
|
||||
|
|
|
@ -6,8 +6,8 @@ macro_rules! avm2_stub_method {
|
|||
linkme::distributed_slice($crate::stub::KNOWN_STUBS)
|
||||
)]
|
||||
static STUB: $crate::stub::Stub = $crate::stub::Stub::Avm2Method {
|
||||
class: $class,
|
||||
method: $method,
|
||||
class: std::borrow::Cow::Borrowed($class),
|
||||
method: std::borrow::Cow::Borrowed($method),
|
||||
specifics: None,
|
||||
};
|
||||
$activation.context.stub_tracker.encounter(&STUB);
|
||||
|
@ -18,9 +18,9 @@ macro_rules! avm2_stub_method {
|
|||
linkme::distributed_slice($crate::stub::KNOWN_STUBS)
|
||||
)]
|
||||
static STUB: $crate::stub::Stub = $crate::stub::Stub::Avm2Method {
|
||||
class: $class,
|
||||
method: $method,
|
||||
specifics: Some($specifics),
|
||||
class: std::borrow::Cow::Borrowed($class),
|
||||
method: std::borrow::Cow::Borrowed($method),
|
||||
specifics: Some(std::borrow::Cow::Borrowed($specifics)),
|
||||
};
|
||||
$activation.context.stub_tracker.encounter(&STUB);
|
||||
};
|
||||
|
@ -46,8 +46,8 @@ macro_rules! avm2_stub_getter {
|
|||
linkme::distributed_slice($crate::stub::KNOWN_STUBS)
|
||||
)]
|
||||
static STUB: $crate::stub::Stub = $crate::stub::Stub::Avm2Getter {
|
||||
class: $class,
|
||||
property: $property,
|
||||
class: std::borrow::Cow::Borrowed($class),
|
||||
property: std::borrow::Cow::Borrowed($property),
|
||||
};
|
||||
$activation.context.stub_tracker.encounter(&STUB);
|
||||
};
|
||||
|
@ -61,8 +61,8 @@ macro_rules! avm2_stub_setter {
|
|||
linkme::distributed_slice($crate::stub::KNOWN_STUBS)
|
||||
)]
|
||||
static STUB: $crate::stub::Stub = $crate::stub::Stub::Avm2Setter {
|
||||
class: $class,
|
||||
property: $property,
|
||||
class: std::borrow::Cow::Borrowed($class),
|
||||
property: std::borrow::Cow::Borrowed($property),
|
||||
};
|
||||
$activation.context.stub_tracker.encounter(&STUB);
|
||||
};
|
||||
|
|
|
@ -7,6 +7,23 @@ use std::fmt::{Debug, Display, Formatter};
|
|||
#[linkme::distributed_slice]
|
||||
pub static KNOWN_STUBS: [Stub] = [..];
|
||||
|
||||
#[cfg(feature = "known_stubs")]
|
||||
mod external {
|
||||
include!(concat!(env!("OUT_DIR"), "/actionscript_stubs.rs"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "known_stubs")]
|
||||
pub fn get_known_stubs() -> FnvHashSet<&'static Stub> {
|
||||
let mut result = FnvHashSet::default();
|
||||
for stub in KNOWN_STUBS.iter() {
|
||||
result.insert(stub);
|
||||
}
|
||||
for stub in external::AS_DEFINED_STUBS {
|
||||
result.insert(stub);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Clone)]
|
||||
pub enum Stub {
|
||||
Avm1Method {
|
||||
|
@ -15,17 +32,17 @@ pub enum Stub {
|
|||
specifics: Option<&'static str>,
|
||||
},
|
||||
Avm2Method {
|
||||
class: &'static str,
|
||||
method: &'static str,
|
||||
specifics: Option<&'static str>,
|
||||
class: Cow<'static, str>,
|
||||
method: Cow<'static, str>,
|
||||
specifics: Option<Cow<'static, str>>,
|
||||
},
|
||||
Avm2Getter {
|
||||
class: &'static str,
|
||||
property: &'static str,
|
||||
class: Cow<'static, str>,
|
||||
property: Cow<'static, str>,
|
||||
},
|
||||
Avm2Setter {
|
||||
class: &'static str,
|
||||
property: &'static str,
|
||||
class: Cow<'static, str>,
|
||||
property: Cow<'static, str>,
|
||||
},
|
||||
Avm2Constructor {
|
||||
class: &'static str,
|
||||
|
|
Loading…
Reference in New Issue