avm2: Move some builtin classes to `Toplevel.as`

Ensure that those classes are loaded before the other classes.
This commit is contained in:
Lord-McSweeney 2024-09-06 18:36:59 -07:00
parent 506b6a9ad2
commit 4b34efaa54
12 changed files with 156 additions and 117 deletions

View File

@ -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<SwfMovie>,
) {
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
}

View File

@ -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)?;

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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"

View File

@ -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)
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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 {

View File

@ -1,6 +1,4 @@
package flash.text.engine {
namespace AS3 = "http://adobe.com/AS3/2006/builtin";
import __ruffle__.stub_method;
import flash.events.EventDispatcher;

View File

@ -1,7 +1,6 @@
package flash.xml
{
namespace AS3 = "http://adobe.com/AS3/2006/builtin";
import flash.xml.XMLNode;
import flash.xml.XMLNodeType;

View File

@ -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"