From 4b34efaa5401cd0f7a20c360e0faa0fcaef8d759 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Fri, 6 Sep 2024 18:36:59 -0700 Subject: [PATCH] avm2: Move some builtin classes to `Toplevel.as` Ensure that those classes are loaded before the other classes. --- core/src/avm2.rs | 45 ++++- core/src/avm2/globals.rs | 159 +++++++++--------- core/src/avm2/globals/Namespace.as | 2 +- core/src/avm2/globals/QName.as | 2 +- core/src/avm2/globals/Toplevel.as | 23 +++ core/src/avm2/globals/XML.as | 4 +- core/src/avm2/globals/XMLList.as | 2 +- .../flash/filters/ColorMatrixFilter.as | 1 - .../avm2/globals/flash/net/URLVariables.as | 2 - .../globals/flash/text/engine/GroupElement.as | 2 - .../src/avm2/globals/flash/xml/XMLDocument.as | 1 - core/src/avm2/globals/globals.as | 30 +--- 12 files changed, 156 insertions(+), 117 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index cf997bb05..f6ab6a847 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -4,7 +4,9 @@ use std::rc::Rc; use crate::avm2::class::AllocatorFn; use crate::avm2::error::make_error_1107; -use crate::avm2::globals::{SystemClassDefs, SystemClasses}; +use crate::avm2::globals::{ + init_builtin_system_classes, init_native_system_classes, SystemClassDefs, SystemClasses, +}; use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::scope::ScopeChain; use crate::avm2::script::{Script, TranslationUnit}; @@ -648,8 +650,7 @@ impl<'gc> Avm2<'gc> { activation.set_outer(ScopeChain::new(domain)); let num_scripts = abc.scripts.len(); - let tunit = - TranslationUnit::from_abc(abc, domain, name, movie, activation.context.gc_context); + let tunit = TranslationUnit::from_abc(abc, domain, name, movie, activation.gc()); tunit.load_classes(&mut activation)?; for i in 0..num_scripts { tunit.load_script(i as u32, &mut activation)?; @@ -661,6 +662,44 @@ impl<'gc> Avm2<'gc> { Ok(None) } + /// Load the playerglobal ABC file. + pub fn load_builtin_abc( + context: &mut UpdateContext<'gc>, + data: &[u8], + domain: Domain<'gc>, + movie: Arc, + ) { + let mut reader = Reader::new(data); + let abc = match reader.read() { + Ok(abc) => abc, + Err(_) => panic!("Builtin ABC should be valid"), + }; + + let mut activation = Activation::from_domain(context, domain); + // Make sure we have the correct domain for code that tries to access it + // using `activation.domain()` + activation.set_outer(ScopeChain::new(domain)); + + let tunit = TranslationUnit::from_abc(abc, domain, None, movie, activation.gc()); + tunit + .load_classes(&mut activation) + .expect("Classes should load"); + + // The second script (script #1) is Toplevel.as, and includes important + // builtin classes such as Namespace, QName, and XML. + tunit + .load_script(1, &mut activation) + .expect("Script should load"); + init_builtin_system_classes(&mut activation); + + // The first script (script #0) is globals.as, and includes other builtin + // classes that are less critical for the AVM to load. + tunit + .load_script(0, &mut activation) + .expect("Script should load"); + init_native_system_classes(&mut activation); + } + pub fn stage_domain(&self) -> Domain<'gc> { self.stage_domain } diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 278fe9f87..d467d9bd3 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -777,91 +777,49 @@ mod native { 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<'gc>> { - activation.avm2().native_method_table = native::NATIVE_METHOD_TABLE; - activation.avm2().native_instance_allocator_table = native::NATIVE_INSTANCE_ALLOCATOR_TABLE; - activation.avm2().native_super_initializer_table = native::NATIVE_SUPER_INITIALIZER_TABLE; - activation.avm2().native_call_handler_table = native::NATIVE_CALL_HANDLER_TABLE; - - let movie = Arc::new( - SwfMovie::from_data(PLAYERGLOBAL, "file:///".into(), None) - .expect("playerglobal.swf should be valid"), - ); - - let slice = SwfSlice::from(movie.clone()); - - let mut reader = slice.read_from(0); - - let tag_callback = |reader: &mut SwfStream<'_>, tag_code, _tag_len| { - if tag_code == TagCode::DoAbc2 { - let do_abc = reader - .read_do_abc_2() - .expect("playerglobal.swf should be valid"); - Avm2::do_abc( - activation.context, - do_abc.data, - None, - do_abc.flags, - domain, - movie.clone(), - ) - .expect("playerglobal.swf should be valid"); - } else if tag_code != TagCode::End { - panic!("playerglobal should only contain `DoAbc2` tag - found tag {tag_code:?}") - } - Ok(ControlFlow::Continue) - }; - - let _ = tag_utils::decode_tags(&mut reader, tag_callback); - macro_rules! avm2_system_classes_playerglobal { - ($activation:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => { - let activation = $activation; - $( - // Lookup with the highest version, so we we see all defined classes here - let ns = Namespace::package($package, ApiVersion::VM_INTERNAL, &mut activation.borrow_gc()); - let name = QName::new(ns, $class_name); - let class_object = activation.domain().get_defined_value(activation, name).unwrap_or_else(|e| panic!("Failed to lookup {name:?}: {e:?}")); - let class_object = class_object.as_object().unwrap().as_class_object().unwrap(); - let sc = activation.avm2().system_classes.as_mut().unwrap(); - sc.$field = class_object; - )* - } +// 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' +macro_rules! avm2_system_classes_playerglobal { + ($activation:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => { + let activation = $activation; + $( + // Lookup with the highest version, so we we see all defined classes here + let ns = Namespace::package($package, ApiVersion::VM_INTERNAL, &mut activation.borrow_gc()); + let name = QName::new(ns, $class_name); + let class_object = activation.domain().get_defined_value(activation, name).unwrap_or_else(|e| panic!("Failed to lookup {name:?}: {e:?}")); + let class_object = class_object.as_object().unwrap().as_class_object().unwrap(); + let sc = activation.avm2().system_classes.as_mut().unwrap(); + sc.$field = class_object; + )* } +} - macro_rules! avm2_system_class_defs_playerglobal { - ($activation:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => { - let activation = $activation; - $( - // Lookup with the highest version, so we we see all defined classes here - let ns = Namespace::package($package, ApiVersion::VM_INTERNAL, &mut activation.borrow_gc()); - let name = QName::new(ns, $class_name); - let class_object = activation.domain().get_defined_value(activation, name).unwrap_or_else(|e| panic!("Failed to lookup {name:?}: {e:?}")); - let class_def = class_object.as_object().unwrap().as_class_object().unwrap().inner_class_definition(); - let sc = activation.avm2().system_class_defs.as_mut().unwrap(); - sc.$field = class_def; - )* - } +macro_rules! avm2_system_class_defs_playerglobal { + ($activation:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => { + let activation = $activation; + $( + // Lookup with the highest version, so we we see all defined classes here + let ns = Namespace::package($package, ApiVersion::VM_INTERNAL, &mut activation.borrow_gc()); + let name = QName::new(ns, $class_name); + let class_object = activation.domain().get_defined_value(activation, name).unwrap_or_else(|e| panic!("Failed to lookup {name:?}: {e:?}")); + let class_def = class_object.as_object().unwrap().as_class_object().unwrap().inner_class_definition(); + let sc = activation.avm2().system_class_defs.as_mut().unwrap(); + sc.$field = class_def; + )* } +} - // 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' +pub fn init_builtin_system_classes<'gc>(activation: &mut Activation<'_, 'gc>) { avm2_system_classes_playerglobal!( &mut *activation, [ - ("", "Date", date), ("", "Error", error), ("", "ArgumentError", argumenterror), ("", "QName", qname), ("", "EvalError", evalerror), ("", "Namespace", namespace), ("", "RangeError", rangeerror), - ("", "RegExp", regexp), ("", "ReferenceError", referenceerror), ("", "SecurityError", securityerror), ("", "SyntaxError", syntaxerror), @@ -870,6 +828,25 @@ fn load_playerglobal<'gc>( ("", "VerifyError", verifyerror), ("", "XML", xml), ("", "XMLList", xml_list), + ] + ); + + avm2_system_class_defs_playerglobal!( + &mut *activation, + [ + ("", "Namespace", namespace), + ("", "XML", xml), + ("", "XMLList", xml_list), + ] + ); +} + +pub fn init_native_system_classes<'gc>(activation: &mut Activation<'_, 'gc>) { + avm2_system_classes_playerglobal!( + &mut *activation, + [ + ("", "Date", date), + ("", "RegExp", regexp), ("flash.display", "AVM1Movie", avm1movie), ("flash.display", "Bitmap", bitmap), ("flash.display", "BitmapData", bitmapdata), @@ -966,9 +943,6 @@ fn load_playerglobal<'gc>( avm2_system_class_defs_playerglobal!( &mut *activation, [ - ("", "Namespace", namespace), - ("", "XML", xml), - ("", "XMLList", xml_list), ("flash.display", "Bitmap", bitmap), ("flash.display", "BitmapData", bitmapdata), ("flash.display", "IGraphicsData", igraphicsdata), @@ -989,6 +963,41 @@ fn load_playerglobal<'gc>( ("flash.display", "GraphicsStroke", graphicsstroke), ] ); +} + +/// 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<'gc>> { + activation.avm2().native_method_table = native::NATIVE_METHOD_TABLE; + activation.avm2().native_instance_allocator_table = native::NATIVE_INSTANCE_ALLOCATOR_TABLE; + activation.avm2().native_super_initializer_table = native::NATIVE_SUPER_INITIALIZER_TABLE; + activation.avm2().native_call_handler_table = native::NATIVE_CALL_HANDLER_TABLE; + + let movie = Arc::new( + SwfMovie::from_data(PLAYERGLOBAL, "file:///".into(), None) + .expect("playerglobal.swf should be valid"), + ); + + let slice = SwfSlice::from(movie.clone()); + + let mut reader = slice.read_from(0); + + let tag_callback = |reader: &mut SwfStream<'_>, tag_code, _tag_len| { + if tag_code == TagCode::DoAbc2 { + let do_abc = reader + .read_do_abc_2() + .expect("playerglobal.swf should be valid"); + Avm2::load_builtin_abc(activation.context, do_abc.data, domain, movie.clone()); + } else if tag_code != TagCode::End { + panic!("playerglobal should only contain `DoAbc2` tag - found tag {tag_code:?}") + } + Ok(ControlFlow::Continue) + }; + + let _ = tag_utils::decode_tags(&mut reader, tag_callback); // Domain memory must be initialized after playerglobals is loaded because it relies on ByteArray. domain.init_default_domain_memory(activation)?; diff --git a/core/src/avm2/globals/Namespace.as b/core/src/avm2/globals/Namespace.as index 20dd95288..0b45d3acc 100644 --- a/core/src/avm2/globals/Namespace.as +++ b/core/src/avm2/globals/Namespace.as @@ -14,7 +14,7 @@ package { prototype.setPropertyIsEnumerable("toString", false); prototype.setPropertyIsEnumerable("valueOf", false); - public function Namespace(prefix:* = undefined, uri:* = undefined) { + public function Namespace(prefix:* = void 0, uri:* = void 0) { this.init(arguments); } diff --git a/core/src/avm2/globals/QName.as b/core/src/avm2/globals/QName.as index 7757731ff..7b0df9b6a 100644 --- a/core/src/avm2/globals/QName.as +++ b/core/src/avm2/globals/QName.as @@ -4,7 +4,7 @@ package { public final class QName { public static const length:* = 2; - public function QName(uri:* = undefined, localName:* = undefined) { + public function QName(uri:* = void 0, localName:* = void 0) { this.init(arguments); } diff --git a/core/src/avm2/globals/Toplevel.as b/core/src/avm2/globals/Toplevel.as index 2b943244b..9787710f0 100644 --- a/core/src/avm2/globals/Toplevel.as +++ b/core/src/avm2/globals/Toplevel.as @@ -26,3 +26,26 @@ package { public native function trace(... rest):void; } + +// These classes are required by other core code, so we put them here. Toplevel.as +// is loaded before the rest of the global code. + +include "Error.as" + +include "ArgumentError.as" +include "DefinitionError.as" +include "EvalError.as" +include "TypeError.as" +include "RangeError.as" +include "ReferenceError.as" +include "SecurityError.as" +include "SyntaxError.as" +include "UninitializedError.as" +include "URIError.as" +include "VerifyError.as" + +include "JSON.as" +include "Namespace.as" +include "QName.as" +include "XML.as" +include "XMLList.as" diff --git a/core/src/avm2/globals/XML.as b/core/src/avm2/globals/XML.as index 7117e36f1..251e5cb53 100644 --- a/core/src/avm2/globals/XML.as +++ b/core/src/avm2/globals/XML.as @@ -45,7 +45,7 @@ package { }; } - public function XML(value:* = undefined) { + public function XML(value:* = void 0) { this.init(value, XML.ignoreComments, XML.ignoreProcessingInstructions, XML.ignoreWhitespace); } @@ -304,7 +304,7 @@ package { return XML.AS3::settings(); } - XML.setSettings = function(v:* = undefined) { + XML.setSettings = function(v:* = void 0) { XML.AS3::setSettings(v) } diff --git a/core/src/avm2/globals/XMLList.as b/core/src/avm2/globals/XMLList.as index 253742a2b..63d940f36 100644 --- a/core/src/avm2/globals/XMLList.as +++ b/core/src/avm2/globals/XMLList.as @@ -3,7 +3,7 @@ package { [Ruffle(CallHandler)] public final dynamic class XMLList { - public function XMLList(value:* = undefined) { + public function XMLList(value:* = void 0) { this.init(value, XML.ignoreComments, XML.ignoreProcessingInstructions, XML.ignoreWhitespace); } diff --git a/core/src/avm2/globals/flash/filters/ColorMatrixFilter.as b/core/src/avm2/globals/flash/filters/ColorMatrixFilter.as index f7e59c305..87bb2f56a 100644 --- a/core/src/avm2/globals/flash/filters/ColorMatrixFilter.as +++ b/core/src/avm2/globals/flash/filters/ColorMatrixFilter.as @@ -1,5 +1,4 @@ package flash.filters { - namespace AS3 = "http://adobe.com/AS3/2006/builtin"; public final class ColorMatrixFilter extends BitmapFilter { private var _matrix: Array; diff --git a/core/src/avm2/globals/flash/net/URLVariables.as b/core/src/avm2/globals/flash/net/URLVariables.as index 068aaf155..bc8822763 100644 --- a/core/src/avm2/globals/flash/net/URLVariables.as +++ b/core/src/avm2/globals/flash/net/URLVariables.as @@ -1,6 +1,4 @@ package flash.net { - namespace AS3 = "http://adobe.com/AS3/2006/builtin"; - import flash.utils.escapeMultiByte; import flash.utils.unescapeMultiByte; public dynamic class URLVariables { diff --git a/core/src/avm2/globals/flash/text/engine/GroupElement.as b/core/src/avm2/globals/flash/text/engine/GroupElement.as index 9ec0332a1..89fb9f41c 100644 --- a/core/src/avm2/globals/flash/text/engine/GroupElement.as +++ b/core/src/avm2/globals/flash/text/engine/GroupElement.as @@ -1,6 +1,4 @@ package flash.text.engine { - namespace AS3 = "http://adobe.com/AS3/2006/builtin"; - import __ruffle__.stub_method; import flash.events.EventDispatcher; diff --git a/core/src/avm2/globals/flash/xml/XMLDocument.as b/core/src/avm2/globals/flash/xml/XMLDocument.as index ed9b74b73..35917dba2 100644 --- a/core/src/avm2/globals/flash/xml/XMLDocument.as +++ b/core/src/avm2/globals/flash/xml/XMLDocument.as @@ -1,7 +1,6 @@ package flash.xml { -namespace AS3 = "http://adobe.com/AS3/2006/builtin"; import flash.xml.XMLNode; import flash.xml.XMLNodeType; diff --git a/core/src/avm2/globals/globals.as b/core/src/avm2/globals/globals.as index 71a50a17c..7e08f7f0b 100644 --- a/core/src/avm2/globals/globals.as +++ b/core/src/avm2/globals/globals.as @@ -1,34 +1,11 @@ // List is ordered alphabetically, except where superclasses/interfaces // need to come before subclasses and implementations. -package { - // This names 'self.AS3::SomeMethod()' calls in 'XML.as' use a 'callproperty' - // opcode, instead of a weird dynamic lookup of the 'AS3' namespace - namespace AS3 = "http://adobe.com/AS3/2006/builtin"; -} - include "__ruffle__/stubs.as" -include "Error.as" - -include "ArgumentError.as" -include "DefinitionError.as" -include "JSON.as" -include "EvalError.as" -include "TypeError.as" -include "Math.as" -include "Namespace.as" -include "QName.as" -include "RangeError.as" -include "ReferenceError.as" -include "RegExp.as" -include "SecurityError.as" -include "SyntaxError.as" -include "UninitializedError.as" -include "URIError.as" -include "VerifyError.as" - include "Date.as" +include "Math.as" +include "RegExp.as" include "avmplus.as" @@ -463,6 +440,3 @@ include "flash/utils/Timer.as" include "flash/xml/XMLNodeType.as" include "flash/xml/XMLNode.as" // XMLDocument extends XMLNode, so XMLNode needs to come before it. include "flash/xml/XMLDocument.as" - -include "XML.as" -include "XMLList.as"