diff --git a/Cargo.lock b/Cargo.lock index a227d9836..2b2230447 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4989,6 +4989,15 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "stub-report" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "ruffle_core", +] + [[package]] name = "swf" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index d2ae24175..f7e861d0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ members = [ "ruffle_gc_arena", + "stub-report", + "video", "video/software", @@ -47,6 +49,8 @@ naga = { version = "0.19.0", features = ["wgsl-out"] } naga_oil = "0.12.0" wgpu = "0.19.1" egui = "0.25.0" +clap = { version = "4.4.18", features = ["derive"] } +anyhow = "1.0" [workspace.lints.rust] # Clippy nightly often adds new/buggy lints that we want to ignore. diff --git a/core/src/avm2.rs b/core/src/avm2.rs index c5d828c54..0847c9e80 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -58,6 +58,7 @@ mod qname; mod regexp; mod scope; mod script; +#[cfg(feature = "known_stubs")] pub mod specification; mod string; mod stubs; diff --git a/core/src/avm2/domain.rs b/core/src/avm2/domain.rs index 78d0d2960..8126fdb89 100644 --- a/core/src/avm2/domain.rs +++ b/core/src/avm2/domain.rs @@ -12,7 +12,6 @@ use crate::avm2::Multiname; use crate::avm2::QName; use gc_arena::{Collect, GcCell, GcWeakCell, Mutation}; use ruffle_wstr::WStr; -use std::cell::Ref; use super::class::Class; use super::error::error; @@ -349,10 +348,6 @@ impl<'gc> Domain<'gc> { Ref::map(self.0.read(), |this| &this.defs) } - pub fn classes(&self) -> Ref>>> { - Ref::map(self.0.read(), |this| &this.classes) - } - pub fn is_default_domain_memory(&self) -> bool { let read = self.0.read(); read.domain_memory.expect("Missing domain memory").as_ptr() diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index 7dd51810f..85439fe02 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -148,7 +148,7 @@ impl<'gc> ScriptObjectData<'gc> { /// Retrieve the values stored directly on this ScriptObjectData. /// /// This should only be used for debugging purposes. - pub fn values(&self) -> &DynamicMap, Value<'gc>> { + pub fn values(&self) -> &DynamicMap, Value<'gc>> { &self.values } diff --git a/core/src/avm2/specification.rs b/core/src/avm2/specification.rs index 6fc16f94d..aa2b00123 100644 --- a/core/src/avm2/specification.rs +++ b/core/src/avm2/specification.rs @@ -1,4 +1,4 @@ -use crate::avm2::dynamic_map::{DynamicMap, StringOrObject}; +use crate::avm2::dynamic_map::DynamicKey; use crate::avm2::function::Executable; use crate::avm2::method::{Method, ParamConfig}; use crate::avm2::object::TObject; @@ -11,6 +11,7 @@ use fnv::{FnvHashMap, FnvHashSet}; use serde::Serialize; use std::borrow::Cow; use std::fs::File; +use std::path::Path; use std::process::exit; fn is_false(b: &bool) -> bool { @@ -298,12 +299,14 @@ impl Definition { let prototype = class_object.prototype(); let prototype_base = prototype.base(); - let prototype_values: &DynamicMap, Value<'gc>> = - prototype_base.values(); + let prototype_values = prototype_base.values(); for (key, value) in prototype_values.as_hashmap().iter() { let name = match key { - StringOrObject::String(name) => *name, - StringOrObject::Object(object) => { + DynamicKey::String(name) => *name, + DynamicKey::Uint(key) => { + AvmString::new_utf8(activation.context.gc_context, key.to_string()) + } + DynamicKey::Object(object) => { Value::Object(*object).coerce_to_string(activation).unwrap() } }; @@ -321,13 +324,13 @@ impl Definition { activation.avm2(), class.read().class_traits(), &mut definition.static_traits, - &stubs, + stubs, ); Self::fill_traits( activation.avm2(), class.read().instance_traits(), &mut definition.instance_traits, - &stubs, + stubs, ); definition @@ -454,10 +457,12 @@ impl Definition { } } -pub fn capture_specification(context: &mut UpdateContext) { - let mut definitions = FnvHashMap::::default(); +#[allow(unreachable_code, unused_variables, clippy::diverging_sub_expression)] +pub fn capture_specification(context: &mut UpdateContext, output: &Path) { let stubs = crate::stub::get_known_stubs(); + let mut definitions = FnvHashMap::::default(); + let defs = context.avm2.playerglobals_domain.defs().clone(); let mut activation = Activation::from_nothing(context.reborrow()); for (name, namespace, _) in defs.iter() { @@ -510,7 +515,7 @@ pub fn capture_specification(context: &mut UpdateContext) { ); } } - serde_json::to_writer_pretty(&File::create("implementation.json").unwrap(), &definitions) - .unwrap(); + serde_json::to_writer_pretty(&File::create(output).unwrap(), &definitions).unwrap(); + tracing::info!("Wrote stub report to {output:?}"); exit(0); } diff --git a/core/src/player.rs b/core/src/player.rs index efe744a12..71f9076d3 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -5,10 +5,7 @@ use crate::avm1::Object; use crate::avm1::SystemProperties; use crate::avm1::VariableDumper; use crate::avm1::{Activation, ActivationIdentifier}; -use crate::avm1::{ScriptObject, TObject, Value}; use crate::avm1::{TObject, Value}; -use crate::avm2::api_version::ApiVersion; -use crate::avm2::specification::capture_specification; use crate::avm2::{ object::TObject as _, Activation as Avm2Activation, Avm2, CallStack, Object as Avm2Object, }; @@ -2124,6 +2121,8 @@ pub struct PlayerBuilder { frame_rate: Option, external_interface_providers: Vec>, fs_command_provider: Box, + #[cfg(feature = "known_stubs")] + stub_report_output: Option, } impl PlayerBuilder { @@ -2172,6 +2171,8 @@ impl PlayerBuilder { frame_rate: None, external_interface_providers: vec![], fs_command_provider: Box::new(NullFsCommandProvider), + #[cfg(feature = "known_stubs")] + stub_report_output: None, } } @@ -2361,6 +2362,14 @@ impl PlayerBuilder { self } + #[cfg(feature = "known_stubs")] + /// Sets the output path for the stub report. When set, the player + /// will write the report to this path and exit the process. + pub fn with_stub_report_output(mut self, output: std::path::PathBuf) -> Self { + self.stub_report_output = Some(output); + self + } + fn create_gc_root<'gc>( gc_context: &'gc gc_arena::Mutation<'gc>, player_version: u8, @@ -2551,7 +2560,10 @@ impl PlayerBuilder { stage.set_allow_fullscreen(context, self.allow_fullscreen); stage.post_instantiation(context, None, Instantiator::Movie, false); stage.build_matrices(context); - capture_specification(context); + #[cfg(feature = "known_stubs")] + if let Some(stub_path) = self.stub_report_output { + crate::avm2::specification::capture_specification(context, &stub_path); + } }); player_lock.gc_arena.borrow().mutate(|context, root| { let call_stack = root.data.read().avm2.call_stack(); diff --git a/exporter/Cargo.toml b/exporter/Cargo.toml index a67bcf997..fdf1bd487 100644 --- a/exporter/Cargo.toml +++ b/exporter/Cargo.toml @@ -11,7 +11,7 @@ version.workspace = true workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { workspace = true } futures = "0.3" ruffle_core = { path = "../core", features = ["deterministic", "default_font"] } ruffle_render_wgpu = { path = "../render/wgpu", features = ["clap"] } @@ -19,8 +19,8 @@ image = { version = "0.24.8", default-features = false, features = ["png"] } log = "0.4" walkdir = "2.4.0" indicatif = "0.17" -anyhow = "1.0" rayon = "1.8.1" +anyhow = { workspace = true } [features] avm_debug = ["ruffle_core/avm_debug"] diff --git a/stub-report/Cargo.toml b/stub-report/Cargo.toml new file mode 100644 index 000000000..e06649e35 --- /dev/null +++ b/stub-report/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "stub-report" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +clap = { workspace = true } +anyhow = { workspace = true } +ruffle_core = { path = "../core", features = ["known_stubs"] } + +[lints] +workspace = true diff --git a/stub-report/src/main.rs b/stub-report/src/main.rs new file mode 100644 index 000000000..8e0bbaa98 --- /dev/null +++ b/stub-report/src/main.rs @@ -0,0 +1,21 @@ +use std::path::PathBuf; + +use anyhow::Result; +use clap::Parser; +use ruffle_core::PlayerBuilder; + +#[derive(Parser, Debug)] +#[clap(name = "Ruffle Stub Report Generator", author, version)] +struct Opt { + /// The file to store the stub report output + #[clap(name = "output")] + output_path: PathBuf, +} + +fn main() -> Result<()> { + let opt: Opt = Opt::parse(); + PlayerBuilder::new() + .with_stub_report_output(opt.output_path) + .build(); + Ok(()) +}