avm2: Implement namespace versioning

This allows us to hide things like `MovieClip.isPlaying` from older
SWFs, which may define an `isPlaying` method without marking it
as an override.

This has the largest impact on public namespaces. There is no longer
just one public namespace - we now have a public namespace per API
version. Most callsites should use `Avm2.find_public_namespace()`,
which determines the public namespace based on the root SWF API version.
This ensures that explicit property access in Ruffle (e.g. calling
"toString") are able to see things defined in the user SWF.

This requires several changes other:

* A new `ApiVersion` enum is introduced, storing all of the api versions
  defined in avmplus
* NamespaceData::Namespace now holds an `ApiVersion`. When we read a
  namespace from a user-defined ABC file, we use the `ApiVersion` from
  the root movie clip. This matches Flash Player's behavior - when a
  child SWF is loaded through `Loader`, its API version gets overwritten
  with the parent's API version, which affects definition visibility and
  'override' requirements in the child SWF. Note that 'behavior changes'
  in methods like `gotoAndPlay` are unaffected.
* The `PartialEq` impl for `Namespace` has been removed - each call site must now choose
  to compare namespaces either by an exact version match, or by
  'compatible' versions (a `<=` check) with `Namespace::matches_ns`
* `PropertyMap` now uses `Namespace::matches_ns` to check for a
  compatible version when looking up a definition.
* When compiling our playerglobal, we pass in the `-apiversioning` flag,
  which causes asc.jar to convert `[API]` metadata into a special
  Unicode 'version mark' suffix in namespace URLs. We parse this
  when loading namespaces to determine the API version to use for
  playerglobal definitions (unmarked definitions use the lowest possible
  version).

Unfortunately, this makes ffdec unable to decompile our
playerglobal.swc
I've going to file an upstream issue
This commit is contained in:
Aaron Hill 2023-07-31 17:39:42 -04:00 committed by Nathan Adams
parent 4d99459849
commit fa05c66e2a
74 changed files with 880 additions and 273 deletions

View File

@ -52,6 +52,10 @@ pub fn build_playerglobal(
&asc_path.to_string_lossy(), &asc_path.to_string_lossy(),
"macromedia.asc.embedding.ScriptCompiler", "macromedia.asc.embedding.ScriptCompiler",
"-optimize", "-optimize",
"-builtin",
"-apiversioning",
"-version",
"9",
"-outdir", "-outdir",
&out_dir.to_string_lossy(), &out_dir.to_string_lossy(),
"-out", "-out",
@ -114,18 +118,54 @@ fn resolve_multiname_name<'a>(abc: &'a AbcFile, multiname: &Multiname) -> &'a st
} }
} }
// Strips off the version mark inserted by 'asc.jar',
// giving us a valid Rust module name. The actual versioning logic
// is handling in Ruffle when we load playerglobals
fn strip_version_mark(val: &str) -> &str {
const MIN_API_MARK: usize = 0xE000;
const MAX_API_MARK: usize = 0xF8FF;
if let Some(chr) = val.chars().last() {
if chr as usize >= MIN_API_MARK && chr as usize <= MAX_API_MARK {
// The version mark is 3 bytes in utf-8
return &val[..val.len() - 3];
}
}
val
}
// Like `resolve_multiname_name`, but for namespaces instead. // Like `resolve_multiname_name`, but for namespaces instead.
fn resolve_multiname_ns<'a>(abc: &'a AbcFile, multiname: &Multiname) -> &'a str { fn resolve_multiname_ns<'a>(abc: &'a AbcFile, multiname: &Multiname) -> &'a str {
if let Multiname::QName { namespace, .. } = multiname { let ns = match multiname {
let ns = &abc.constant_pool.namespaces[namespace.0 as usize - 1]; Multiname::QName { namespace, .. } => {
if let Namespace::Package(p) | Namespace::PackageInternal(p) = ns { &abc.constant_pool.namespaces[namespace.0 as usize - 1]
&abc.constant_pool.strings[p.0 as usize - 1]
} else {
panic!("Unexpected Namespace {ns:?}");
} }
Multiname::Multiname { namespace_set, .. } => {
if namespace_set.0 == 0 {
panic!("Multiname namespace set must not be null");
}
let actual_index = namespace_set.0 as usize - 1;
let ns_set = abc
.constant_pool
.namespace_sets
.get(actual_index)
.unwrap_or_else(|| panic!("Unknown namespace set constant {}", actual_index));
if ns_set.len() == 1 {
&abc.constant_pool.namespaces[ns_set[0].0 as usize - 1]
} else {
panic!("Found multiple namespaces in namespace set {ns_set:?}")
}
}
_ => panic!("Unexpected Multiname {multiname:?}"),
};
let namespace = if let Namespace::Package(p) | Namespace::PackageInternal(p) = ns {
&abc.constant_pool.strings[p.0 as usize - 1]
} else { } else {
panic!("Unexpected Multiname {multiname:?}"); panic!("Unexpected Namespace {ns:?}");
} };
strip_version_mark(namespace)
} }
fn flash_to_rust_path(path: &str) -> String { fn flash_to_rust_path(path: &str) -> String {

View File

@ -29,6 +29,7 @@ macro_rules! avm_debug {
pub mod activation; pub mod activation;
mod amf; mod amf;
pub mod api_version;
mod array; mod array;
pub mod bytearray; pub mod bytearray;
mod call_stack; mod call_stack;
@ -77,8 +78,10 @@ pub use crate::avm2::object::{
pub use crate::avm2::qname::QName; pub use crate::avm2::qname::QName;
pub use crate::avm2::value::Value; pub use crate::avm2::value::Value;
use self::api_version::ApiVersion;
use self::object::WeakObject; use self::object::WeakObject;
use self::scope::Scope; use self::scope::Scope;
use num_traits::FromPrimitive;
const BROADCAST_WHITELIST: [&str; 4] = ["enterFrame", "exitFrame", "frameConstructed", "render"]; const BROADCAST_WHITELIST: [&str; 4] = ["enterFrame", "exitFrame", "frameConstructed", "render"];
@ -113,7 +116,13 @@ pub struct Avm2<'gc> {
/// However, it's not strictly defined which items end up there. /// However, it's not strictly defined which items end up there.
toplevel_global_object: Option<Object<'gc>>, toplevel_global_object: Option<Object<'gc>>,
pub public_namespace: Namespace<'gc>, /// The public namespace, versioned with `ApiVersion::ALL_VERSIONS`.
/// When calling into user code, you should almost always use `find_public_namespace`
/// instead, as it will return the correct version for the current call stack.
public_namespace_base_version: Namespace<'gc>,
// FIXME - make this an enum map once gc-arena supports it
public_namespaces: Vec<Namespace<'gc>>,
public_namespace_vm_internal: Namespace<'gc>,
pub internal_namespace: Namespace<'gc>, pub internal_namespace: Namespace<'gc>,
pub as3_namespace: Namespace<'gc>, pub as3_namespace: Namespace<'gc>,
pub vector_public_namespace: Namespace<'gc>, pub vector_public_namespace: Namespace<'gc>,
@ -156,6 +165,14 @@ pub struct Avm2<'gc> {
/// strong references around (this matches Flash's behavior). /// strong references around (this matches Flash's behavior).
orphan_objects: Rc<Vec<DisplayObjectWeak<'gc>>>, orphan_objects: Rc<Vec<DisplayObjectWeak<'gc>>>,
/// The api version of our root movie clip. Note - this is used as the
/// api version for swfs loaded via `Loader`, overriding the api version
/// specified in the loaded SWF. This is only used for API versioning (hiding
/// definitions from playerglobals) - version-specific behavior in things like
/// `gotoAndPlay` uses the current movie clip's SWF version.
#[collect(require_static)]
pub root_api_version: ApiVersion,
#[cfg(feature = "avm_debug")] #[cfg(feature = "avm_debug")]
pub debug_output: bool, pub debug_output: bool,
} }
@ -167,6 +184,10 @@ impl<'gc> Avm2<'gc> {
let stage_domain = let stage_domain =
Domain::uninitialized_domain(context.gc_context, Some(playerglobals_domain)); Domain::uninitialized_domain(context.gc_context, Some(playerglobals_domain));
let public_namespaces = (0..=(ApiVersion::VM_INTERNAL as usize))
.map(|val| Namespace::package("", ApiVersion::from_usize(val).unwrap(), context))
.collect();
Self { Self {
player_version, player_version,
stack: Vec::new(), stack: Vec::new(),
@ -177,13 +198,24 @@ impl<'gc> Avm2<'gc> {
system_classes: None, system_classes: None,
toplevel_global_object: None, toplevel_global_object: None,
public_namespace: Namespace::package("", context), public_namespace_base_version: Namespace::package("", ApiVersion::AllVersions, context),
public_namespaces,
public_namespace_vm_internal: Namespace::package("", ApiVersion::VM_INTERNAL, context),
internal_namespace: Namespace::internal("", context), internal_namespace: Namespace::internal("", context),
as3_namespace: Namespace::package("http://adobe.com/AS3/2006/builtin", context), as3_namespace: Namespace::package(
vector_public_namespace: Namespace::package("__AS3__.vec", context), "http://adobe.com/AS3/2006/builtin",
ApiVersion::AllVersions,
context,
),
vector_public_namespace: Namespace::package(
"__AS3__.vec",
ApiVersion::AllVersions,
context,
),
vector_internal_namespace: Namespace::internal("__AS3__.vec", context), vector_internal_namespace: Namespace::internal("__AS3__.vec", context),
proxy_namespace: Namespace::package( proxy_namespace: Namespace::package(
"http://www.adobe.com/2006/actionscript/flash/proxy", "http://www.adobe.com/2006/actionscript/flash/proxy",
ApiVersion::AllVersions,
context, context,
), ),
// these are required to facilitate shared access between Rust and AS // these are required to facilitate shared access between Rust and AS
@ -201,6 +233,9 @@ impl<'gc> Avm2<'gc> {
orphan_objects: Default::default(), orphan_objects: Default::default(),
// Set the lowest version for now - this be overriden when we set our movie
root_api_version: ApiVersion::AllVersions,
#[cfg(feature = "avm_debug")] #[cfg(feature = "avm_debug")]
debug_output: false, debug_output: false,
} }
@ -639,6 +674,13 @@ impl<'gc> Avm2<'gc> {
#[cfg(not(feature = "avm_debug"))] #[cfg(not(feature = "avm_debug"))]
pub const fn set_show_debug_output(&self, _visible: bool) {} pub const fn set_show_debug_output(&self, _visible: bool) {}
/// Gets the public namespace, versioned based on the current root SWF.
/// See `AvmCore::findPublicNamespace()`
/// https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/AvmCore.cpp#L5809C25-L5809C25
pub fn find_public_namespace(&self) -> Namespace<'gc> {
self.public_namespaces[self.root_api_version as usize]
}
} }
/// If the provided `DisplayObjectWeak` should have frames run, returns /// If the provided `DisplayObjectWeak` should have frames run, returns

View File

@ -761,7 +761,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
) -> Result<Namespace<'gc>, Error<'gc>> { ) -> Result<Namespace<'gc>, Error<'gc>> {
method method
.translation_unit() .translation_unit()
.pool_namespace(index, &mut self.borrow_gc()) .pool_namespace(index, &mut self.context)
} }
/// Retrieve a multiname from the current constant pool. /// Retrieve a multiname from the current constant pool.
@ -772,7 +772,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> { ) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
method method
.translation_unit() .translation_unit()
.pool_maybe_uninitialized_multiname(index, &mut self.borrow_gc()) .pool_maybe_uninitialized_multiname(index, &mut self.context)
} }
/// Retrieve a multiname from the current constant pool. /// Retrieve a multiname from the current constant pool.
@ -784,7 +784,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> { ) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
let name = method let name = method
.translation_unit() .translation_unit()
.pool_maybe_uninitialized_multiname(index, &mut self.borrow_gc())?; .pool_maybe_uninitialized_multiname(index, &mut self.context)?;
if name.has_lazy_component() { if name.has_lazy_component() {
let name = name.fill_with_runtime_params(self)?; let name = name.fill_with_runtime_params(self)?;
Ok(Gc::new(self.context.gc_context, name)) Ok(Gc::new(self.context.gc_context, name))
@ -804,7 +804,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> { ) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
method method
.translation_unit() .translation_unit()
.pool_multiname_static(index, &mut self.borrow_gc()) .pool_multiname_static(index, &mut self.context)
} }
/// Retrieve a static, or non-runtime, multiname from the current constant /// Retrieve a static, or non-runtime, multiname from the current constant
@ -818,7 +818,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> { ) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
method method
.translation_unit() .translation_unit()
.pool_multiname_static_any(index, &mut self.borrow_gc()) .pool_multiname_static_any(index, &mut self.context)
} }
/// Retrieve a method entry from the current ABC file's method table. /// Retrieve a method entry from the current ABC file's method table.
@ -1652,7 +1652,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
} }
let name = name_value.coerce_to_string(self)?; let name = name_value.coerce_to_string(self)?;
let multiname = Multiname::new(self.avm2().public_namespace, name); let multiname = Multiname::new(self.avm2().find_public_namespace(), name);
let has_prop = obj.has_property_via_in(self, &multiname)?; let has_prop = obj.has_property_via_in(self, &multiname)?;
self.push_stack(has_prop); self.push_stack(has_prop);
@ -1672,11 +1672,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
// for `finally` scopes, FP just creates a bare object. // for `finally` scopes, FP just creates a bare object.
self.avm2().classes().object.construct(self, &[])? self.avm2().classes().object.construct(self, &[])?
} else { } else {
let qname = QName::from_abc_multiname( let qname =
method.translation_unit(), QName::from_abc_multiname(method.translation_unit(), vname, &mut self.context)?;
vname,
&mut self.borrow_gc(),
)?;
ScriptObject::catch_scope(self.context.gc_context, &qname) ScriptObject::catch_scope(self.context.gc_context, &qname)
}; };
self.push_stack(so); self.push_stack(so);

View File

@ -0,0 +1,96 @@
// Based on https://github.com/adobe/avmplus/blob/master/core/api-versions.h
// FIXME - if we ever add in AIR emulation, then implement the 'version series' logic.
// For now, it's fine to just compare Flash player version against air versions directly.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, FromPrimitive)]
#[allow(non_camel_case_types)]
pub enum ApiVersion {
AllVersions = 0,
AIR_1_0 = 1,
FP_10_0 = 2,
AIR_1_5 = 3,
AIR_1_5_1 = 4,
FP_10_0_32 = 5,
AIR_1_5_2 = 6,
FP_10_1 = 7,
AIR_2_0 = 8,
AIR_2_5 = 9,
FP_10_2 = 10,
AIR_2_6 = 11,
SWF_12 = 12,
AIR_2_7 = 13,
SWF_13 = 14,
AIR_3_0 = 15,
SWF_14 = 16,
AIR_3_1 = 17,
SWF_15 = 18,
AIR_3_2 = 19,
SWF_16 = 20,
AIR_3_3 = 21,
SWF_17 = 22,
AIR_3_4 = 23,
SWF_18 = 24,
AIR_3_5 = 25,
SWF_19 = 26,
AIR_3_6 = 27,
SWF_20 = 28,
AIR_3_7 = 29,
SWF_21 = 30,
AIR_3_8 = 31,
SWF_22 = 32,
AIR_3_9 = 33,
SWF_23 = 34,
AIR_4_0 = 35,
SWF_24 = 36,
AIR_13_0 = 37,
SWF_25 = 38,
AIR_14_0 = 39,
SWF_26 = 40,
AIR_15_0 = 41,
SWF_27 = 42,
AIR_16_0 = 43,
SWF_28 = 44,
AIR_17_0 = 45,
SWF_29 = 46,
AIR_18_0 = 47,
SWF_30 = 48,
AIR_19_0 = 49,
SWF_31 = 50,
AIR_20_0 = 51,
VM_INTERNAL = 52,
}
impl ApiVersion {
pub fn from_swf_version(val: u8) -> Option<ApiVersion> {
match val {
// There's no specific entry for SWF 9 in avmplus,
// so map it to the lowest entry.
9 => Some(ApiVersion::AllVersions),
10 => Some(ApiVersion::FP_10_1),
11 => Some(ApiVersion::FP_10_2),
12 => Some(ApiVersion::SWF_12),
13 => Some(ApiVersion::SWF_13),
14 => Some(ApiVersion::SWF_14),
15 => Some(ApiVersion::SWF_15),
16 => Some(ApiVersion::SWF_16),
17 => Some(ApiVersion::SWF_17),
18 => Some(ApiVersion::SWF_18),
19 => Some(ApiVersion::SWF_19),
20 => Some(ApiVersion::SWF_20),
21 => Some(ApiVersion::SWF_21),
22 => Some(ApiVersion::SWF_22),
23 => Some(ApiVersion::SWF_23),
24 => Some(ApiVersion::SWF_24),
25 => Some(ApiVersion::SWF_25),
26 => Some(ApiVersion::SWF_26),
27 => Some(ApiVersion::SWF_27),
28 => Some(ApiVersion::SWF_28),
29 => Some(ApiVersion::SWF_29),
30 => Some(ApiVersion::SWF_30),
31 => Some(ApiVersion::SWF_31),
// We haven't yet created entries from higher versions - just map them
// to the highest non-VM_INTERNAL version.
_ if val > 31 => Some(ApiVersion::SWF_31),
_ => None,
}
}
}

View File

@ -303,20 +303,19 @@ impl<'gc> Class<'gc> {
.ok_or_else(|| "LoadError: Instance index not valid".into()); .ok_or_else(|| "LoadError: Instance index not valid".into());
let abc_instance = abc_instance?; let abc_instance = abc_instance?;
let mut context = activation.borrow_gc(); let name = QName::from_abc_multiname(unit, abc_instance.name, &mut activation.context)?;
let name = QName::from_abc_multiname(unit, abc_instance.name, &mut context)?;
let super_class = if abc_instance.super_name.0 == 0 { let super_class = if abc_instance.super_name.0 == 0 {
None None
} else { } else {
Some( Some(
unit.pool_multiname_static(abc_instance.super_name, &mut context)? unit.pool_multiname_static(abc_instance.super_name, &mut activation.context)?
.deref() .deref()
.clone(), .clone(),
) )
}; };
let protected_namespace = if let Some(ns) = &abc_instance.protected_namespace { let protected_namespace = if let Some(ns) = &abc_instance.protected_namespace {
Some(unit.pool_namespace(*ns, &mut context)?) Some(unit.pool_namespace(*ns, &mut activation.context)?)
} else { } else {
None None
}; };
@ -324,7 +323,7 @@ impl<'gc> Class<'gc> {
let mut interfaces = Vec::with_capacity(abc_instance.interfaces.len()); let mut interfaces = Vec::with_capacity(abc_instance.interfaces.len());
for interface_name in &abc_instance.interfaces { for interface_name in &abc_instance.interfaces {
interfaces.push( interfaces.push(
unit.pool_multiname_static(*interface_name, &mut context)? unit.pool_multiname_static(*interface_name, &mut activation.context)?
.deref() .deref()
.clone(), .clone(),
); );
@ -344,7 +343,7 @@ impl<'gc> Class<'gc> {
// When loading a class from our playerglobal, grab the corresponding native // When loading a class from our playerglobal, grab the corresponding native
// allocator function from the table (which may be `None`) // allocator function from the table (which may be `None`)
if unit.domain().is_playerglobals_domain(activation) { if unit.domain().is_playerglobals_domain(activation.avm2()) {
instance_allocator = activation.avm2().native_instance_allocator_table instance_allocator = activation.avm2().native_instance_allocator_table
[class_index as usize] [class_index as usize]
.map(|(_name, ptr)| Allocator(ptr)); .map(|(_name, ptr)| Allocator(ptr));
@ -461,8 +460,9 @@ impl<'gc> Class<'gc> {
if let Some(superclass) = superclass { if let Some(superclass) = superclass {
for instance_trait in self.instance_traits.iter() { for instance_trait in self.instance_traits.iter() {
let is_protected = let is_protected = self.protected_namespace().map_or(false, |prot| {
self.protected_namespace() == Some(instance_trait.name().namespace()); prot.exact_version_match(instance_trait.name().namespace())
});
let mut current_superclass = Some(superclass); let mut current_superclass = Some(superclass);
let mut did_override = false; let mut did_override = false;
@ -476,9 +476,11 @@ impl<'gc> Class<'gc> {
let my_name = instance_trait.name(); let my_name = instance_trait.name();
let names_match = super_name.local_name() == my_name.local_name() let names_match = super_name.local_name() == my_name.local_name()
&& (super_name.namespace() == my_name.namespace() && (super_name.namespace().matches_ns(my_name.namespace())
|| (is_protected || (is_protected
&& read.protected_namespace() == Some(super_name.namespace()))); && read.protected_namespace().map_or(false, |prot| {
prot.exact_version_match(super_name.namespace())
})));
if names_match { if names_match {
match (supertrait.kind(), instance_trait.kind()) { match (supertrait.kind(), instance_trait.kind()) {
//Getter/setter pairs do NOT override one another //Getter/setter pairs do NOT override one another
@ -538,7 +540,7 @@ impl<'gc> Class<'gc> {
Ok(GcCell::new( Ok(GcCell::new(
activation.context.gc_context, activation.context.gc_context,
Self { Self {
name: QName::new(activation.avm2().public_namespace, name), name: QName::new(activation.avm2().public_namespace_base_version, name),
param: None, param: None,
super_class: None, super_class: None,
attributes: ClassAttributes::empty(), attributes: ClassAttributes::empty(),
@ -601,7 +603,7 @@ impl<'gc> Class<'gc> {
for &(name, value) in items { for &(name, value) in items {
self.define_class_trait(Trait::from_const( self.define_class_trait(Trait::from_const(
QName::new(namespace, name), QName::new(namespace, name),
Multiname::new(activation.avm2().public_namespace, "Number"), Multiname::new(activation.avm2().public_namespace_base_version, "Number"),
Some(value.into()), Some(value.into()),
)); ));
} }
@ -616,7 +618,7 @@ impl<'gc> Class<'gc> {
for &(name, value) in items { for &(name, value) in items {
self.define_class_trait(Trait::from_const( self.define_class_trait(Trait::from_const(
QName::new(namespace, name), QName::new(namespace, name),
Multiname::new(activation.avm2().public_namespace, "uint"), Multiname::new(activation.avm2().public_namespace_base_version, "uint"),
Some(value.into()), Some(value.into()),
)); ));
} }
@ -631,7 +633,7 @@ impl<'gc> Class<'gc> {
for &(name, value) in items { for &(name, value) in items {
self.define_class_trait(Trait::from_const( self.define_class_trait(Trait::from_const(
QName::new(namespace, name), QName::new(namespace, name),
Multiname::new(activation.avm2().public_namespace, "int"), Multiname::new(activation.avm2().public_namespace_base_version, "int"),
Some(value.into()), Some(value.into()),
)); ));
} }
@ -747,7 +749,7 @@ impl<'gc> Class<'gc> {
for &(name, value) in items { for &(name, value) in items {
self.define_instance_trait(Trait::from_slot( self.define_instance_trait(Trait::from_slot(
QName::new(namespace, name), QName::new(namespace, name),
Multiname::new(activation.avm2().public_namespace, "Number"), Multiname::new(activation.avm2().public_namespace_base_version, "Number"),
value.map(|v| v.into()), value.map(|v| v.into()),
)); ));
} }

View File

@ -14,6 +14,7 @@ use ruffle_wstr::WStr;
use super::class::Class; use super::class::Class;
use super::error::error; use super::error::error;
use super::string::AvmString; use super::string::AvmString;
use super::Avm2;
/// Represents a set of scripts and movies that share traits across different /// Represents a set of scripts and movies that share traits across different
/// script-global scopes. /// script-global scopes.
@ -69,8 +70,8 @@ impl<'gc> Domain<'gc> {
)) ))
} }
pub fn is_playerglobals_domain(&self, activation: &mut Activation<'_, 'gc>) -> bool { pub fn is_playerglobals_domain(&self, avm2: &Avm2<'gc>) -> bool {
activation.avm2().playerglobals_domain.0.as_ptr() == self.0.as_ptr() avm2.playerglobals_domain.0.as_ptr() == self.0.as_ptr()
} }
/// Create a new domain with a given parent. /// Create a new domain with a given parent.
@ -252,12 +253,14 @@ impl<'gc> Domain<'gc> {
)); ));
name = "__AS3__.vec::Vector".into(); name = "__AS3__.vec::Vector".into();
} }
let name = QName::from_qualified_name(name, activation); // FIXME - is this the correct api version?
let api_version = activation.avm2().root_api_version;
let name = QName::from_qualified_name(name, api_version, activation);
let res = self.get_defined_value(activation, name); let res = self.get_defined_value(activation, name);
if let Some(type_name) = type_name { if let Some(type_name) = type_name {
let type_qname = QName::from_qualified_name(type_name, activation); let type_qname = QName::from_qualified_name(type_name, api_version, activation);
let type_class = self.get_defined_value(activation, type_qname)?; let type_class = self.get_defined_value(activation, type_qname)?;
if let Ok(res) = res { if let Ok(res) = res {
let class = res.as_object().ok_or_else(|| { let class = res.as_object().ok_or_else(|| {
@ -292,11 +295,16 @@ impl<'gc> Domain<'gc> {
/// Export a class into the current application domain. /// Export a class into the current application domain.
/// ///
/// This does nothing if the definition already exists in this domain or a parent. /// This does nothing if the definition already exists in this domain or a parent.
pub fn export_class(&self, class: GcCell<'gc, Class<'gc>>, mc: &Mutation<'gc>) { pub fn export_class(
if self.has_class(class.read().name()) { &self,
export_name: QName<'gc>,
class: GcCell<'gc, Class<'gc>>,
mc: &Mutation<'gc>,
) {
if self.has_class(export_name) {
return; return;
} }
self.0.write(mc).classes.insert(class.read().name(), class); self.0.write(mc).classes.insert(export_name, class);
} }
pub fn is_default_domain_memory(&self) -> bool { pub fn is_default_domain_memory(&self) -> bool {

View File

@ -1248,11 +1248,11 @@ pub fn string_to_multiname<'gc>(
) -> Multiname<'gc> { ) -> Multiname<'gc> {
if let Some(name) = name.strip_prefix(b'@') { if let Some(name) = name.strip_prefix(b'@') {
let name = AvmString::new(activation.context.gc_context, name); let name = AvmString::new(activation.context.gc_context, name);
Multiname::attribute(activation.avm2().public_namespace, name) Multiname::attribute(activation.avm2().public_namespace_base_version, name)
} else if &*name == b"*" { } else if &*name == b"*" {
Multiname::any(activation.context.gc_context) Multiname::any(activation.context.gc_context)
} else { } else {
Multiname::new(activation.avm2().public_namespace, name) Multiname::new(activation.avm2().public_namespace_base_version, name)
} }
} }

View File

@ -1,4 +1,5 @@
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::api_version::ApiVersion;
use crate::avm2::class::Class; use crate::avm2::class::Class;
use crate::avm2::domain::Domain; use crate::avm2::domain::Domain;
use crate::avm2::object::{ClassObject, Object, ScriptObject, TObject}; use crate::avm2::object::{ClassObject, Object, ScriptObject, TObject};
@ -310,7 +311,11 @@ fn define_fn_on_global<'gc>(
) { ) {
let (_, global, domain) = script.init(); let (_, global, domain) = script.init();
let qname = QName::new( let qname = QName::new(
Namespace::package(package, &mut activation.borrow_gc()), Namespace::package(
package,
ApiVersion::AllVersions,
&mut activation.borrow_gc(),
),
name, name,
); );
let func = domain let func = domain
@ -391,8 +396,7 @@ fn class<'gc>(
activation.avm2().classes().class, activation.avm2().classes().class,
); );
domain.export_definition(class_name, script, mc); domain.export_definition(class_name, script, mc);
domain.export_class(class_def, mc); domain.export_class(class_name, class_def, mc);
Ok(class_object) Ok(class_object)
} }
@ -478,24 +482,24 @@ pub fn load_player_globals<'gc>(
let object_classdef = object::create_class(activation); let object_classdef = object::create_class(activation);
let object_class = ClassObject::from_class_partial(activation, object_classdef, None)?; let object_class = ClassObject::from_class_partial(activation, object_classdef, None)?;
let object_proto = ScriptObject::custom_object(mc, Some(object_class), None); let object_proto = ScriptObject::custom_object(mc, Some(object_class), None);
domain.export_class(object_classdef, mc); domain.export_class(object_classdef.read().name(), object_classdef, mc);
let fn_classdef = function::create_class(activation); let fn_classdef = function::create_class(activation);
let fn_class = ClassObject::from_class_partial(activation, fn_classdef, Some(object_class))?; let fn_class = ClassObject::from_class_partial(activation, fn_classdef, Some(object_class))?;
let fn_proto = ScriptObject::custom_object(mc, Some(fn_class), Some(object_proto)); let fn_proto = ScriptObject::custom_object(mc, Some(fn_class), Some(object_proto));
domain.export_class(fn_classdef, mc); domain.export_class(fn_classdef.read().name(), fn_classdef, mc);
let class_classdef = class::create_class(activation); let class_classdef = class::create_class(activation);
let class_class = let class_class =
ClassObject::from_class_partial(activation, class_classdef, Some(object_class))?; ClassObject::from_class_partial(activation, class_classdef, Some(object_class))?;
let class_proto = ScriptObject::custom_object(mc, Some(object_class), Some(object_proto)); let class_proto = ScriptObject::custom_object(mc, Some(object_class), Some(object_proto));
domain.export_class(class_classdef, mc); domain.export_class(class_classdef.read().name(), class_classdef, mc);
let global_classdef = global_scope::create_class(activation); let global_classdef = global_scope::create_class(activation);
let global_class = let global_class =
ClassObject::from_class_partial(activation, global_classdef, Some(object_class))?; ClassObject::from_class_partial(activation, global_classdef, Some(object_class))?;
let global_proto = ScriptObject::custom_object(mc, Some(object_class), Some(object_proto)); let global_proto = ScriptObject::custom_object(mc, Some(object_class), Some(object_proto));
domain.export_class(global_classdef, mc); domain.export_class(global_classdef.read().name(), global_classdef, mc);
// Now to weave the Gordian knot... // Now to weave the Gordian knot...
object_class.link_prototype(activation, object_proto)?; object_class.link_prototype(activation, object_proto)?;
@ -691,7 +695,8 @@ fn load_playerglobal<'gc>(
($activation:expr, $script:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => { ($activation:expr, $script:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => {
let activation = $activation; let activation = $activation;
$( $(
let ns = Namespace::package($package, &mut activation.borrow_gc()); // 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 name = QName::new(ns, $class_name);
let class_object = activation.domain().get_defined_value(activation, name)?; let class_object = activation.domain().get_defined_value(activation, name)?;
let class_object = class_object.as_object().unwrap().as_class_object().unwrap(); let class_object = class_object.as_object().unwrap().as_class_object().unwrap();

View File

@ -63,6 +63,28 @@ class is loaded.
See `flash/events/Event.as` for an example See `flash/events/Event.as` for an example
## API Versioning
Ruffle supports Flash's Api versioning, which hides newer playerglobal definitions
(including methods/properties) from SWFs compiled with older API versions.
For example, see `Event.WORKER_STATE`
To add versioning to an API:
1. Determine the first version where it was added. This can be seen in the Flash Documentation (e.g. "Runtime Versions: Flash Player 11.4, AIR 3.4")
2. Convert the Flash Player version to an SWF version number using [this chart](https://github.com/ruffle-rs/ruffle/wiki/SWF-version-chart)
2. Determine the corresponding asc.jar version code for the SWF version. This can be found in avmplus in https://github.com/adobe/avmplus/blob/master/core/api-versions.as
3. Add an `[API("VersionCode")]` metadata to the defintion. In the `Event.WORKER_STATE` example,
this looks like:
```actionscript
[API("682")]
public static const WORKER_STATE:String = "workerState";
```
WORKER_STATE was added in Flash Player 11.4, which corresponds to SWF version 17. Looking at the avmplus file, this corresponds
to a version code of "682".
## Compiling ## Compiling
Java must be installed for the build process to complete. Java must be installed for the build process to complete.

View File

@ -1252,8 +1252,11 @@ pub fn remove_at<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let class = Class::new( let class = Class::new(
QName::new(activation.avm2().public_namespace, "Array"), QName::new(activation.avm2().public_namespace_base_version, "Array"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(instance_init, "<Array instance initializer>", mc), Method::from_builtin(instance_init, "<Array instance initializer>", mc),
Method::from_builtin(class_init, "<Array class initializer>", mc), Method::from_builtin(class_init, "<Array class initializer>", mc),
mc, mc,
@ -1271,7 +1274,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
)] = &[("length", Some(length), Some(set_length))]; )] = &[("length", Some(length), Some(set_length))];
write.define_builtin_instance_properties( write.define_builtin_instance_properties(
mc, mc,
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
PUBLIC_INSTANCE_PROPERTIES, PUBLIC_INSTANCE_PROPERTIES,
); );
@ -1295,14 +1298,14 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
("UNIQUESORT", SortOptions::UNIQUE_SORT.bits() as u32), ("UNIQUESORT", SortOptions::UNIQUE_SORT.bits() as u32),
]; ];
write.define_constant_uint_class_traits( write.define_constant_uint_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CONSTANTS_UINT, CONSTANTS_UINT,
activation, activation,
); );
const CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)]; const CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)];
write.define_constant_int_class_traits( write.define_constant_int_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CONSTANTS_INT, CONSTANTS_INT,
activation, activation,
); );

View File

@ -4,7 +4,7 @@ use crate::avm2::method::Method;
use crate::avm2::object::{ArrayObject, TObject}; use crate::avm2::object::{ArrayObject, TObject};
use crate::avm2::parameters::ParametersExt; use crate::avm2::parameters::ParametersExt;
use crate::avm2::property::Property; use crate::avm2::property::Property;
use crate::avm2::ClassObject; use crate::avm2::{ClassObject, Namespace};
use crate::avm2::{Activation, Error, Object, Value}; use crate::avm2::{Activation, Error, Object, Value};
use crate::avm2_stub_method; use crate::avm2_stub_method;
@ -193,33 +193,22 @@ fn describe_internal_body<'gc>(
// Implement the weird 'HIDE_NSURI_METHODS' behavior from avmplus: // Implement the weird 'HIDE_NSURI_METHODS' behavior from avmplus:
// https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/TypeDescriber.cpp#L237 // https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/TypeDescriber.cpp#L237
let mut skip_ns = Vec::new(); let mut skip_ns: Vec<Namespace<'_>> = Vec::new();
if let Some(super_vtable) = super_vtable { if let Some(super_vtable) = super_vtable {
for (_, ns, prop) in super_vtable.resolved_traits().iter() { for (_, ns, prop) in super_vtable.resolved_traits().iter() {
if !ns.as_uri().is_empty() { if !ns.as_uri().is_empty() {
if let Property::Method { disp_id } = prop { if let Property::Method { .. } = prop {
let method = super_vtable if !skip_ns
.get_full_method(*disp_id) .iter()
.unwrap_or_else(|| panic!("Missing method for id {disp_id:?}")); .any(|other_ns| other_ns.exact_version_match(ns))
let is_playerglobals = method {
.class skip_ns.push(ns);
.class_scope()
.domain()
.is_playerglobals_domain(activation);
if !skip_ns.contains(&(ns, is_playerglobals)) {
skip_ns.push((ns, is_playerglobals));
} }
} }
} }
} }
} }
let class_is_playerglobals = class_obj
.class_scope()
.domain()
.is_playerglobals_domain(activation);
// FIXME - avmplus iterates over their own hashtable, so the order in the final XML // FIXME - avmplus iterates over their own hashtable, so the order in the final XML
// is different // is different
for (prop_name, ns, prop) in vtable.resolved_traits().iter() { for (prop_name, ns, prop) in vtable.resolved_traits().iter() {
@ -227,35 +216,10 @@ fn describe_internal_body<'gc>(
continue; continue;
} }
// Hack around our lack of namespace versioning.
// This is hack to work around the fact that we don't have namespace versioning
// Once we do, methods from playerglobals should end up distinct public and AS3
// namespaces, due to the special `kApiVersion_VM_ALLVERSIONS` used:
// https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/AbcParser.cpp#L1497
//
// The main way this is
// observable is by having a class like this:
//
// ``
// class SubClass extends SuperClass {
// AS3 function subclassMethod {}
// }
// class SuperClass {}
// ```
//
// Here, `subclassMethod` will not get hidden - even though `Object`
// has AS3 methods, they are in the playerglobal AS3 namespace
// (with version kApiVersion_VM_ALLVERSIONS), which is distinct
// from the AS3 namespace used by SubClass. However, if we have any
// user-defined classes in the inheritance chain, then the namespace
// *should* match (if the swf version numbers match).
//
// For now, we approximate this by checking if the declaring class
// and our starting class are both in the playerglobals domain
// or both not in the playerglobals domain. If not, then we ignore
// `skip_ns`, since we should really have two different namespaces here.
if flags.contains(DescribeTypeFlags::HIDE_NSURI_METHODS) if flags.contains(DescribeTypeFlags::HIDE_NSURI_METHODS)
&& skip_ns.contains(&(ns, class_is_playerglobals)) && skip_ns
.iter()
.any(|other_ns| ns.exact_version_match(*other_ns))
{ {
continue; continue;
} }

View File

@ -129,8 +129,11 @@ fn value_of<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let class = Class::new( let class = Class::new(
QName::new(activation.avm2().public_namespace, "Boolean"), QName::new(activation.avm2().public_namespace_base_version, "Boolean"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(instance_init, "<Boolean instance initializer>", mc), Method::from_builtin(instance_init, "<Boolean instance initializer>", mc),
Method::from_builtin(class_init, "<Boolean class initializer>", mc), Method::from_builtin(class_init, "<Boolean class initializer>", mc),
mc, mc,
@ -160,7 +163,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
const CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)]; const CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)];
write.define_constant_int_class_traits( write.define_constant_int_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CONSTANTS_INT, CONSTANTS_INT,
activation, activation,
); );

View File

@ -47,8 +47,11 @@ fn prototype<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let gc_context = activation.context.gc_context; let gc_context = activation.context.gc_context;
let class_class = Class::new( let class_class = Class::new(
QName::new(activation.avm2().public_namespace, "Class"), QName::new(activation.avm2().public_namespace_base_version, "Class"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(instance_init, "<Class instance initializer>", gc_context), Method::from_builtin(instance_init, "<Class instance initializer>", gc_context),
Method::from_builtin(class_init, "<Class class initializer>", gc_context), Method::from_builtin(class_init, "<Class class initializer>", gc_context),
gc_context, gc_context,
@ -60,7 +63,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
// We need to define it, since it shows up in 'describeType' // We need to define it, since it shows up in 'describeType'
const CLASS_CONSTANTS: &[(&str, i32)] = &[("length", 1)]; const CLASS_CONSTANTS: &[(&str, i32)] = &[("length", 1)];
write.define_constant_int_class_traits( write.define_constant_int_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CLASS_CONSTANTS, CLASS_CONSTANTS,
activation, activation,
); );
@ -72,7 +75,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
)] = &[("prototype", Some(prototype), None)]; )] = &[("prototype", Some(prototype), None)];
write.define_builtin_instance_properties( write.define_builtin_instance_properties(
gc_context, gc_context,
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
PUBLIC_INSTANCE_PROPERTIES, PUBLIC_INSTANCE_PROPERTIES,
); );

View File

@ -1326,8 +1326,11 @@ pub fn parse<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let class = Class::new( let class = Class::new(
QName::new(activation.avm2().public_namespace, "Date"), QName::new(activation.avm2().public_namespace_base_version, "Date"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(instance_init, "<Date instance initializer>", mc), Method::from_builtin(instance_init, "<Date instance initializer>", mc),
Method::from_builtin(class_init, "<Date class initializer>", mc), Method::from_builtin(class_init, "<Date class initializer>", mc),
mc, mc,
@ -1371,7 +1374,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
]; ];
write.define_builtin_instance_properties( write.define_builtin_instance_properties(
mc, mc,
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
PUBLIC_INSTANCE_PROPERTIES, PUBLIC_INSTANCE_PROPERTIES,
); );
write.define_builtin_instance_methods( write.define_builtin_instance_methods(
@ -1384,14 +1387,14 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
write.define_builtin_class_methods( write.define_builtin_class_methods(
mc, mc,
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
PUBLIC_CLASS_METHODS, PUBLIC_CLASS_METHODS,
); );
const CLASS_CONSTANTS_INT: &[(&str, i32)] = &[("length", 7)]; const CLASS_CONSTANTS_INT: &[(&str, i32)] = &[("length", 7)];
write.define_constant_int_class_traits( write.define_constant_int_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CLASS_CONSTANTS_INT, CLASS_CONSTANTS_INT,
activation, activation,
); );

View File

@ -53,7 +53,9 @@ package flash.display {
public native function get y():Number; public native function get y():Number;
public native function set y(value:Number):void; public native function set y(value:Number):void;
[API("662")]
public native function get z():Number; public native function get z():Number;
[API("662")]
public native function set z(value:Number):void; public native function set z(value:Number):void;
public native function get rotation():Number; public native function get rotation():Number;

View File

@ -10,6 +10,7 @@ package flash.display {
public native function get enabled():Boolean; public native function get enabled():Boolean;
public native function set enabled(value:Boolean):void; public native function set enabled(value:Boolean):void;
public native function get framesLoaded():int; public native function get framesLoaded():int;
[API("674")]
public native function get isPlaying():Boolean; public native function get isPlaying():Boolean;
public native function get scenes():Array; public native function get scenes():Array;
public native function get totalFrames():int; public native function get totalFrames():int;

View File

@ -85,6 +85,7 @@ package flash.events {
public static const CHANNEL_STATE:String = "channelState"; public static const CHANNEL_STATE:String = "channelState";
[API("682")]
public static const WORKER_STATE:String = "workerState"; public static const WORKER_STATE:String = "workerState";
public function Event(type:String, bubbles:Boolean = false, cancelable:Boolean = false) { public function Event(type:String, bubbles:Boolean = false, cancelable:Boolean = false) {

View File

@ -152,7 +152,7 @@ pub fn to_string<'gc>(
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let object_proto = activation.avm2().classes().object.prototype(); let object_proto = activation.avm2().classes().object.prototype();
let name = Multiname::new(activation.avm2().public_namespace, "toString"); let name = Multiname::new(activation.avm2().public_namespace_base_version, "toString");
object_proto object_proto
.get_property(&name, activation)? .get_property(&name, activation)?

View File

@ -1,5 +1,6 @@
//! `flash.net.SharedObject` builtin/prototype //! `flash.net.SharedObject` builtin/prototype
use crate::avm2::api_version::ApiVersion;
use crate::avm2::error::error; use crate::avm2::error::error;
use crate::avm2::object::TObject; use crate::avm2::object::TObject;
use crate::avm2::Error::AvmError; use crate::avm2::Error::AvmError;
@ -147,7 +148,11 @@ pub fn get_local<'gc>(
// Set the internal name // Set the internal name
let ruffle_name = Multiname::new( let ruffle_name = Multiname::new(
Namespace::package("__ruffle__", &mut activation.borrow_gc()), Namespace::package(
"__ruffle__",
ApiVersion::AllVersions,
&mut activation.borrow_gc(),
),
"_ruffleName", "_ruffleName",
); );
this.set_property( this.set_property(
@ -194,7 +199,11 @@ pub fn flush<'gc>(
.coerce_to_object(activation)?; .coerce_to_object(activation)?;
let ruffle_name = Multiname::new( let ruffle_name = Multiname::new(
Namespace::package("__ruffle__", &mut activation.borrow_gc()), Namespace::package(
"__ruffle__",
ApiVersion::AllVersions,
&mut activation.borrow_gc(),
),
"_ruffleName", "_ruffleName",
); );
let name = this let name = this
@ -231,7 +240,11 @@ pub fn get_size<'gc>(
.coerce_to_object(activation)?; .coerce_to_object(activation)?;
let ruffle_name = Multiname::new( let ruffle_name = Multiname::new(
Namespace::package("__ruffle__", &mut activation.borrow_gc()), Namespace::package(
"__ruffle__",
ApiVersion::AllVersions,
&mut activation.borrow_gc(),
),
"_ruffleName", "_ruffleName",
); );
let name = this let name = this
@ -274,7 +287,11 @@ pub fn clear<'gc>(
// Delete data from storage backend. // Delete data from storage backend.
let ruffle_name = Multiname::new( let ruffle_name = Multiname::new(
Namespace::package("__ruffle__", &mut activation.borrow_gc()), Namespace::package(
"__ruffle__",
ApiVersion::AllVersions,
&mut activation.borrow_gc(),
),
"_ruffleName", "_ruffleName",
); );
let name = this let name = this

View File

@ -50,7 +50,7 @@ pub fn get_parent_domain<'gc>(
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
if let Some(appdomain) = this.as_application_domain() { if let Some(appdomain) = this.as_application_domain() {
if let Some(parent_domain) = appdomain.parent_domain() { if let Some(parent_domain) = appdomain.parent_domain() {
if parent_domain.is_playerglobals_domain(activation) { if parent_domain.is_playerglobals_domain(activation.avm2()) {
return Ok(Value::Null); return Ok(Value::Null);
} }
return Ok(DomainObject::from_domain(activation, parent_domain)?.into()); return Ok(DomainObject::from_domain(activation, parent_domain)?.into());

View File

@ -1,7 +1,6 @@
//! `flash.utils` namespace //! `flash.utils` namespace
use crate::avm2::object::TObject; use crate::avm2::object::TObject;
use crate::avm2::{Activation, Error, Object, Value}; use crate::avm2::{Activation, Error, Object, Value};
use crate::string::AvmString; use crate::string::AvmString;
use crate::string::WString; use crate::string::WString;

View File

@ -182,8 +182,11 @@ fn set_prototype<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let gc_context = activation.context.gc_context; let gc_context = activation.context.gc_context;
let function_class = Class::new( let function_class = Class::new(
QName::new(activation.avm2().public_namespace, "Function"), QName::new(activation.avm2().public_namespace_base_version, "Function"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(instance_init, "<Function instance initializer>", gc_context), Method::from_builtin(instance_init, "<Function instance initializer>", gc_context),
Method::from_builtin(class_init, "<Function class initializer>", gc_context), Method::from_builtin(class_init, "<Function class initializer>", gc_context),
gc_context, gc_context,
@ -209,13 +212,13 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
]; ];
write.define_builtin_instance_properties( write.define_builtin_instance_properties(
gc_context, gc_context,
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
PUBLIC_INSTANCE_PROPERTIES, PUBLIC_INSTANCE_PROPERTIES,
); );
const CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)]; const CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)];
write.define_constant_int_class_traits( write.define_constant_int_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CONSTANTS_INT, CONSTANTS_INT,
activation, activation,
); );

View File

@ -36,8 +36,11 @@ pub fn class_init<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
Class::new( Class::new(
QName::new(activation.avm2().public_namespace, "global"), QName::new(activation.avm2().public_namespace_base_version, "global"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(instance_init, "<global instance initializer>", mc), Method::from_builtin(instance_init, "<global instance initializer>", mc),
Method::from_builtin(class_init, "<global class initializer>", mc), Method::from_builtin(class_init, "<global class initializer>", mc),
mc, mc,

View File

@ -215,8 +215,11 @@ fn value_of<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let class = Class::new( let class = Class::new(
QName::new(activation.avm2().public_namespace, "int"), QName::new(activation.avm2().public_namespace_base_version, "int"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin_and_params( Method::from_builtin_and_params(
instance_init, instance_init,
"<int instance initializer>", "<int instance initializer>",
@ -251,7 +254,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
("length", 1), ("length", 1),
]; ];
write.define_constant_int_class_traits( write.define_constant_int_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CLASS_CONSTANTS, CLASS_CONSTANTS,
activation, activation,
); );

View File

@ -28,16 +28,19 @@ pub fn instance_init<'gc>(
_ => None, _ => None,
}; };
let api_version = activation.avm2().root_api_version;
let namespace = match uri_value { let namespace = match uri_value {
Some(Value::Object(Object::QNameObject(qname))) => qname Some(Value::Object(Object::QNameObject(qname))) => qname
.uri() .uri()
.map(|uri| Namespace::package(uri, &mut activation.borrow_gc())) .map(|uri| Namespace::package(uri, api_version, &mut activation.borrow_gc()))
.unwrap_or_else(|| Namespace::any(activation.context.gc_context)), .unwrap_or_else(|| Namespace::any(activation.context.gc_context)),
Some(val) => Namespace::package( Some(val) => Namespace::package(
val.coerce_to_string(activation)?, val.coerce_to_string(activation)?,
api_version,
&mut activation.borrow_gc(), &mut activation.borrow_gc(),
), ),
None => activation.avm2().public_namespace, None => activation.avm2().public_namespace_base_version,
}; };
this.init_namespace(activation.context.gc_context, namespace); this.init_namespace(activation.context.gc_context, namespace);
@ -105,8 +108,11 @@ pub fn uri<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let class = Class::new( let class = Class::new(
QName::new(activation.avm2().public_namespace, "Namespace"), QName::new(activation.avm2().public_namespace_base_version, "Namespace"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(instance_init, "<Namespace instance initializer>", mc), Method::from_builtin(instance_init, "<Namespace instance initializer>", mc),
Method::from_builtin(class_init, "<Namespace class initializer>", mc), Method::from_builtin(class_init, "<Namespace class initializer>", mc),
mc, mc,
@ -132,13 +138,13 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
)] = &[("prefix", Some(prefix), None), ("uri", Some(uri), None)]; )] = &[("prefix", Some(prefix), None), ("uri", Some(uri), None)];
write.define_builtin_instance_properties( write.define_builtin_instance_properties(
mc, mc,
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
PUBLIC_INSTANCE_PROPERTIES, PUBLIC_INSTANCE_PROPERTIES,
); );
const CONSTANTS_INT: &[(&str, i32)] = &[("length", 2)]; const CONSTANTS_INT: &[(&str, i32)] = &[("length", 2)];
write.define_constant_int_class_traits( write.define_constant_int_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CONSTANTS_INT, CONSTANTS_INT,
activation, activation,
); );

View File

@ -371,8 +371,11 @@ fn value_of<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let class = Class::new( let class = Class::new(
QName::new(activation.avm2().public_namespace, "Number"), QName::new(activation.avm2().public_namespace_base_version, "Number"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(instance_init, "<Number instance initializer>", mc), Method::from_builtin(instance_init, "<Number instance initializer>", mc),
Method::from_builtin(class_init, "<Number class initializer>", mc), Method::from_builtin(class_init, "<Number class initializer>", mc),
mc, mc,
@ -408,14 +411,14 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
("LOG10E", std::f64::consts::LOG10_E), ("LOG10E", std::f64::consts::LOG10_E),
]; ];
write.define_constant_number_class_traits( write.define_constant_number_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CLASS_CONSTANTS_NUMBER, CLASS_CONSTANTS_NUMBER,
activation, activation,
); );
const CLASS_CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)]; const CLASS_CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)];
write.define_constant_int_class_traits( write.define_constant_int_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CLASS_CONSTANTS_INT, CLASS_CONSTANTS_INT,
activation, activation,
); );

View File

@ -254,7 +254,7 @@ pub fn init<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let gc_context = activation.context.gc_context; let gc_context = activation.context.gc_context;
let object_class = Class::new( let object_class = Class::new(
QName::new(activation.avm2().public_namespace, "Object"), QName::new(activation.avm2().public_namespace_base_version, "Object"),
None, None,
Method::from_builtin(instance_init, "<Object instance initializer>", gc_context), Method::from_builtin(instance_init, "<Object instance initializer>", gc_context),
Method::from_builtin(class_init, "<Object class initializer>", gc_context), Method::from_builtin(class_init, "<Object class initializer>", gc_context),
@ -268,8 +268,8 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
)); ));
write.define_class_trait(Trait::from_const( write.define_class_trait(Trait::from_const(
QName::new(activation.avm2().public_namespace, "length"), QName::new(activation.avm2().public_namespace_base_version, "length"),
Multiname::new(activation.avm2().public_namespace, "int"), Multiname::new(activation.avm2().public_namespace_base_version, "int"),
Some(1.into()), Some(1.into()),
)); ));
@ -284,7 +284,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
Multiname::any(activation.context.gc_context), Multiname::any(activation.context.gc_context),
Value::Undefined, Value::Undefined,
)], )],
Multiname::new(activation.avm2().public_namespace, "Boolean"), Multiname::new(activation.avm2().public_namespace_base_version, "Boolean"),
), ),
( (
"isPrototypeOf", "isPrototypeOf",
@ -294,7 +294,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
Multiname::any(activation.context.gc_context), Multiname::any(activation.context.gc_context),
Value::Undefined, Value::Undefined,
)], )],
Multiname::new(activation.avm2().public_namespace, "Boolean"), Multiname::new(activation.avm2().public_namespace_base_version, "Boolean"),
), ),
( (
"propertyIsEnumerable", "propertyIsEnumerable",
@ -304,7 +304,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
Multiname::any(activation.context.gc_context), Multiname::any(activation.context.gc_context),
Value::Undefined, Value::Undefined,
)], )],
Multiname::new(activation.avm2().public_namespace, "Boolean"), Multiname::new(activation.avm2().public_namespace_base_version, "Boolean"),
), ),
]; ];
write.define_builtin_instance_methods_with_sig( write.define_builtin_instance_methods_with_sig(

View File

@ -1,6 +1,7 @@
//! `QName` impl //! `QName` impl
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::api_version::ApiVersion;
use crate::avm2::object::{Object, TObject}; use crate::avm2::object::{Object, TObject};
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
@ -46,16 +47,19 @@ pub fn init<'gc>(
let ns_arg = args.get(0).cloned().unwrap(); let ns_arg = args.get(0).cloned().unwrap();
let local_arg = args.get(1).cloned().unwrap_or(Value::Undefined); let local_arg = args.get(1).cloned().unwrap_or(Value::Undefined);
let api_version = activation.avm2().root_api_version;
let namespace = match ns_arg { let namespace = match ns_arg {
Value::Object(o) if o.as_namespace().is_some() => o.as_namespace().as_deref().copied(), Value::Object(o) if o.as_namespace().is_some() => o.as_namespace().as_deref().copied(),
Value::Object(o) if o.as_qname_object().is_some() => o Value::Object(o) if o.as_qname_object().is_some() => {
.as_qname_object() o.as_qname_object().unwrap().uri().map(|uri| {
.unwrap() Namespace::package(uri, ApiVersion::AllVersions, &mut activation.borrow_gc())
.uri() })
.map(|uri| Namespace::package(uri, &mut activation.borrow_gc())), }
Value::Undefined | Value::Null => None, Value::Undefined | Value::Null => None,
v => Some(Namespace::package( v => Some(Namespace::package(
v.coerce_to_string(activation)?, v.coerce_to_string(activation)?,
api_version,
&mut activation.borrow_gc(), &mut activation.borrow_gc(),
)), )),
}; };
@ -81,7 +85,7 @@ pub fn init<'gc>(
}; };
if &*local != b"*" { if &*local != b"*" {
this.set_local_name(activation.context.gc_context, local); this.set_local_name(activation.context.gc_context, local);
Some(activation.avm2().public_namespace) Some(activation.avm2().find_public_namespace())
} else { } else {
None None
} }

View File

@ -651,8 +651,11 @@ fn to_upper_case<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let class = Class::new( let class = Class::new(
QName::new(activation.avm2().public_namespace, "String"), QName::new(activation.avm2().public_namespace_base_version, "String"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(instance_init, "<String instance initializer>", mc), Method::from_builtin(instance_init, "<String instance initializer>", mc),
Method::from_builtin(class_init, "<String class initializer>", mc), Method::from_builtin(class_init, "<String class initializer>", mc),
mc, mc,
@ -674,7 +677,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
)] = &[("length", Some(length), None)]; )] = &[("length", Some(length), None)];
write.define_builtin_instance_properties( write.define_builtin_instance_properties(
mc, mc,
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
PUBLIC_INSTANCE_PROPERTIES, PUBLIC_INSTANCE_PROPERTIES,
); );
write.define_builtin_instance_methods( write.define_builtin_instance_methods(
@ -685,7 +688,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
const CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)]; const CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)];
write.define_constant_int_class_traits( write.define_constant_int_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CONSTANTS_INT, CONSTANTS_INT,
activation, activation,
); );
@ -695,7 +698,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
write.define_builtin_class_methods(mc, activation.avm2().as3_namespace, AS3_CLASS_METHODS); write.define_builtin_class_methods(mc, activation.avm2().as3_namespace, AS3_CLASS_METHODS);
write.define_builtin_class_methods( write.define_builtin_class_methods(
mc, mc,
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
PUBLIC_CLASS_METHODS, PUBLIC_CLASS_METHODS,
); );

View File

@ -216,8 +216,11 @@ fn value_of<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let class = Class::new( let class = Class::new(
QName::new(activation.avm2().public_namespace, "uint"), QName::new(activation.avm2().public_namespace_base_version, "uint"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin_and_params( Method::from_builtin_and_params(
instance_init, instance_init,
"<uint instance initializer>", "<uint instance initializer>",
@ -251,14 +254,14 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
const CLASS_CONSTANTS_UINT: &[(&str, u32)] = const CLASS_CONSTANTS_UINT: &[(&str, u32)] =
&[("MAX_VALUE", u32::MAX), ("MIN_VALUE", u32::MIN)]; &[("MAX_VALUE", u32::MAX), ("MIN_VALUE", u32::MIN)];
write.define_constant_uint_class_traits( write.define_constant_uint_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CLASS_CONSTANTS_UINT, CLASS_CONSTANTS_UINT,
activation, activation,
); );
const CLASS_CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)]; const CLASS_CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)];
write.define_constant_int_class_traits( write.define_constant_int_class_traits(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
CLASS_CONSTANTS_INT, CLASS_CONSTANTS_INT,
activation, activation,
); );

View File

@ -917,7 +917,10 @@ pub fn create_generic_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let class = Class::new( let class = Class::new(
QName::new(activation.avm2().vector_public_namespace, "Vector"), QName::new(activation.avm2().vector_public_namespace, "Vector"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(generic_init, "<Vector instance initializer>", mc), Method::from_builtin(generic_init, "<Vector instance initializer>", mc),
Method::from_builtin(generic_init, "<Vector class initializer>", mc), Method::from_builtin(generic_init, "<Vector class initializer>", mc),
mc, mc,
@ -950,7 +953,10 @@ pub fn create_builtin_class<'gc>(
let class = Class::new( let class = Class::new(
name, name,
Some(Multiname::new(activation.avm2().public_namespace, "Object")), Some(Multiname::new(
activation.avm2().public_namespace_base_version,
"Object",
)),
Method::from_builtin(instance_init, "<Vector.<T> instance initializer>", mc), Method::from_builtin(instance_init, "<Vector.<T> instance initializer>", mc),
Method::from_builtin(class_init, "<Vector.<T> class initializer>", mc), Method::from_builtin(class_init, "<Vector.<T> class initializer>", mc),
mc, mc,
@ -981,7 +987,7 @@ pub fn create_builtin_class<'gc>(
]; ];
write.define_builtin_instance_properties( write.define_builtin_instance_properties(
mc, mc,
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
PUBLIC_INSTANCE_PROPERTIES, PUBLIC_INSTANCE_PROPERTIES,
); );

View File

@ -23,7 +23,7 @@ fn void_init<'gc>(
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let class = Class::new( let class = Class::new(
QName::new(activation.avm2().public_namespace, "void"), QName::new(activation.avm2().public_namespace_base_version, "void"),
None, None,
Method::from_builtin(void_init, "", mc), Method::from_builtin(void_init, "", mc),
Method::from_builtin(void_init, "", mc), Method::from_builtin(void_init, "", mc),

View File

@ -1,5 +1,6 @@
//! XML builtin and prototype //! XML builtin and prototype
use crate::avm2::api_version::ApiVersion;
use crate::avm2::e4x::{name_to_multiname, E4XNode, E4XNodeKind}; use crate::avm2::e4x::{name_to_multiname, E4XNode, E4XNodeKind};
use crate::avm2::error::type_error; use crate::avm2::error::type_error;
pub use crate::avm2::object::xml_allocator; pub use crate::avm2::object::xml_allocator;
@ -172,7 +173,11 @@ pub fn namespace_internal_impl<'gc>(
// FIXME: Nodes currently either have zero or one namespace, which has the prefix "" (empty string) // FIXME: Nodes currently either have zero or one namespace, which has the prefix "" (empty string)
Ok(match node.namespace() { Ok(match node.namespace() {
Some(ns) if prefix.is_empty() => { Some(ns) if prefix.is_empty() => {
let namespace = Namespace::package(ns, &mut activation.context.borrow_gc()); let namespace = Namespace::package(
ns,
ApiVersion::AllVersions,
&mut activation.context.borrow_gc(),
);
NamespaceObject::from_namespace(activation, namespace)?.into() NamespaceObject::from_namespace(activation, namespace)?.into()
} }
_ => Value::Undefined, _ => Value::Undefined,
@ -422,7 +427,7 @@ pub fn append_child<'gc>(
.expect("Should have an XMLList"); .expect("Should have an XMLList");
let length = xml_list.length(); let length = xml_list.length();
let name = Multiname::new( let name = Multiname::new(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
AvmString::new_utf8(activation.context.gc_context, length.to_string()), AvmString::new_utf8(activation.context.gc_context, length.to_string()),
); );
xml_list.set_property_local(&name, child, activation)?; xml_list.set_property_local(&name, child, activation)?;

View File

@ -63,7 +63,7 @@ impl<'gc> ParamConfig<'gc> {
AvmString::from("<Unnamed Parameter>") AvmString::from("<Unnamed Parameter>")
}; };
let param_type_name = txunit let param_type_name = txunit
.pool_multiname_static_any(config.kind, &mut activation.borrow_gc())? .pool_multiname_static_any(config.kind, &mut activation.context)?
.deref() .deref()
.clone(); .clone();
@ -153,7 +153,7 @@ impl<'gc> BytecodeMethod<'gc> {
} }
let return_type = txunit let return_type = txunit
.pool_multiname_static_any(method.return_type, &mut activation.borrow_gc())? .pool_multiname_static_any(method.return_type, &mut activation.context)?
.deref() .deref()
.clone(); .clone();

View File

@ -4,7 +4,7 @@ use crate::avm2::Error;
use crate::avm2::Namespace; use crate::avm2::Namespace;
use crate::avm2::QName; use crate::avm2::QName;
use crate::avm2::{Object, Value}; use crate::avm2::{Object, Value};
use crate::context::GcContext; use crate::context::UpdateContext;
use crate::string::{AvmString, WStr, WString}; use crate::string::{AvmString, WStr, WString};
use bitflags::bitflags; use bitflags::bitflags;
use gc_arena::Gc; use gc_arena::Gc;
@ -17,7 +17,7 @@ use swf::avm2::types::{
#[derive(Clone, Copy, Debug, Collect)] #[derive(Clone, Copy, Debug, Collect)]
#[collect(no_drop)] #[collect(no_drop)]
enum NamespaceSet<'gc> { pub enum NamespaceSet<'gc> {
Multiple(Gc<'gc, Vec<Namespace<'gc>>>), Multiple(Gc<'gc, Vec<Namespace<'gc>>>),
Single(Namespace<'gc>), Single(Namespace<'gc>),
} }
@ -115,10 +115,10 @@ impl<'gc> Multiname<'gc> {
/// Read a namespace set from the ABC constant pool, and return a list of /// Read a namespace set from the ABC constant pool, and return a list of
/// copied namespaces. /// copied namespaces.
fn abc_namespace_set( pub fn abc_namespace_set(
translation_unit: TranslationUnit<'gc>, translation_unit: TranslationUnit<'gc>,
namespace_set_index: Index<AbcNamespaceSet>, namespace_set_index: Index<AbcNamespaceSet>,
context: &mut GcContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,
) -> Result<NamespaceSet<'gc>, Error<'gc>> { ) -> Result<NamespaceSet<'gc>, Error<'gc>> {
if namespace_set_index.0 == 0 { if namespace_set_index.0 == 0 {
return Err(Error::RustError( return Err(Error::RustError(
@ -153,7 +153,7 @@ impl<'gc> Multiname<'gc> {
pub fn from_abc_index( pub fn from_abc_index(
translation_unit: TranslationUnit<'gc>, translation_unit: TranslationUnit<'gc>,
multiname_index: Index<AbcMultiname>, multiname_index: Index<AbcMultiname>,
context: &mut GcContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,
) -> Result<Self, Error<'gc>> { ) -> Result<Self, Error<'gc>> {
let mc = context.gc_context; let mc = context.gc_context;
let abc = translation_unit.abc(); let abc = translation_unit.abc();
@ -164,7 +164,7 @@ impl<'gc> Multiname<'gc> {
Self { Self {
ns: NamespaceSet::single(translation_unit.pool_namespace(*namespace, context)?), ns: NamespaceSet::single(translation_unit.pool_namespace(*namespace, context)?),
name: translation_unit name: translation_unit
.pool_string_option(name.0, context)? .pool_string_option(name.0, &mut context.borrow_gc())?
.map(|v| v.into()), .map(|v| v.into()),
param: None, param: None,
flags: Default::default(), flags: Default::default(),
@ -173,7 +173,7 @@ impl<'gc> Multiname<'gc> {
AbcMultiname::RTQName { name } | AbcMultiname::RTQNameA { name } => Self { AbcMultiname::RTQName { name } | AbcMultiname::RTQNameA { name } => Self {
ns: NamespaceSet::multiple(vec![], mc), ns: NamespaceSet::multiple(vec![], mc),
name: translation_unit name: translation_unit
.pool_string_option(name.0, context)? .pool_string_option(name.0, &mut context.borrow_gc())?
.map(|v| v.into()), .map(|v| v.into()),
param: None, param: None,
flags: MultinameFlags::HAS_LAZY_NS, flags: MultinameFlags::HAS_LAZY_NS,
@ -194,7 +194,7 @@ impl<'gc> Multiname<'gc> {
} => Self { } => Self {
ns: Self::abc_namespace_set(translation_unit, *namespace_set, context)?, ns: Self::abc_namespace_set(translation_unit, *namespace_set, context)?,
name: translation_unit name: translation_unit
.pool_string_option(name.0, context)? .pool_string_option(name.0, &mut context.borrow_gc())?
.map(|v| v.into()), .map(|v| v.into()),
param: None, param: None,
flags: Default::default(), flags: Default::default(),
@ -389,7 +389,7 @@ impl<'gc> Multiname<'gc> {
let ns_match = self let ns_match = self
.namespace_set() .namespace_set()
.iter() .iter()
.any(|ns| ns.is_any() || *ns == name.namespace()); .any(|ns| ns.is_any() || ns.matches_ns(name.namespace()));
let name_match = self.name.map(|n| n == name.local_name()).unwrap_or(true); let name_match = self.name.map(|n| n == name.local_name()).unwrap_or(true);
ns_match && name_match ns_match && name_match

View File

@ -1,27 +1,19 @@
use crate::avm2::Error; use crate::avm2::Error;
use crate::context::UpdateContext;
use crate::string::{AvmAtom, AvmString}; use crate::string::{AvmAtom, AvmString};
use crate::{avm2::script::TranslationUnit, context::GcContext}; use crate::{avm2::script::TranslationUnit, context::GcContext};
use gc_arena::{Collect, Gc, Mutation}; use gc_arena::{Collect, Gc, Mutation};
use num_traits::FromPrimitive;
use ruffle_wstr::WStr;
use std::fmt::Debug; use std::fmt::Debug;
use swf::avm2::types::{Index, Namespace as AbcNamespace}; use swf::avm2::types::{Index, Namespace as AbcNamespace};
use super::api_version::ApiVersion;
#[derive(Clone, Copy, Collect, Debug)] #[derive(Clone, Copy, Collect, Debug)]
#[collect(no_drop)] #[collect(no_drop)]
pub struct Namespace<'gc>(Gc<'gc, NamespaceData<'gc>>); pub struct Namespace<'gc>(Gc<'gc, NamespaceData<'gc>>);
impl<'gc> PartialEq for Namespace<'gc> {
fn eq(&self, other: &Self) -> bool {
if Gc::as_ptr(self.0) == Gc::as_ptr(other.0) {
true
} else if self.is_private() || other.is_private() {
false
} else {
*self.0 == *other.0
}
}
}
impl<'gc> Eq for Namespace<'gc> {}
/// Represents the name of a namespace. /// Represents the name of a namespace.
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
#[derive(Copy, Clone, Collect, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Collect, Debug, PartialEq, Eq)]
@ -29,7 +21,7 @@ impl<'gc> Eq for Namespace<'gc> {}
enum NamespaceData<'gc> { enum NamespaceData<'gc> {
// note: this is the default "public namespace", corresponding to both // note: this is the default "public namespace", corresponding to both
// ABC Namespace and PackageNamespace // ABC Namespace and PackageNamespace
Namespace(AvmAtom<'gc>), Namespace(AvmAtom<'gc>, #[collect(require_static)] ApiVersion),
PackageInternal(AvmAtom<'gc>), PackageInternal(AvmAtom<'gc>),
Protected(AvmAtom<'gc>), Protected(AvmAtom<'gc>),
Explicit(AvmAtom<'gc>), Explicit(AvmAtom<'gc>),
@ -40,15 +32,47 @@ enum NamespaceData<'gc> {
Any, Any,
} }
fn strip_version_mark(url: &WStr) -> Option<(&WStr, ApiVersion)> {
// See https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/AvmCore.h#L485
const MIN_API_MARK: usize = 0xE000;
const MAX_API_MARK: usize = 0xF8FF;
const WEIRD_START_MARK: usize = 0xE294;
if let Some(Ok(chr)) = url.chars().last() {
let chr = chr as usize;
if chr >= MIN_API_MARK && chr <= MAX_API_MARK {
// Note - sometimes asc.jar emits a version mark of 0xE000.
// We treat this as `AllVersions`
let version = if chr >= WEIRD_START_MARK {
// Note that is an index into the ApiVersion enum, *not* an SWF version
ApiVersion::from_usize(chr - WEIRD_START_MARK)
.unwrap_or_else(|| panic!("Bad version mark {chr}"))
} else {
ApiVersion::AllVersions
};
// Since we had a char in the range 0xE000-0xF8FF, we must
// have a wide WStr, so we can remove this char by just
// removing the last byte
assert!(url.is_wide());
let stripped = &url[..url.len() - 1];
return Some((stripped, version));
}
}
None
}
impl<'gc> Namespace<'gc> { impl<'gc> Namespace<'gc> {
/// Read a namespace declaration from the ABC constant pool and copy it to /// Read a namespace declaration from the ABC constant pool and copy it to
/// a namespace value. /// a namespace value.
/// NOTE: you should use the TranslationUnit.pool_namespace instead of calling this. /// NOTE: you should use the TranslationUnit.pool_namespace instead of calling this.
/// otherwise you run a risk of creating a duplicate of private ns singleton. /// otherwise you run a risk of creating a duplicate of private ns singleton.
/// Based on https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/AbcParser.cpp#L1459
pub fn from_abc_namespace( pub fn from_abc_namespace(
translation_unit: TranslationUnit<'gc>, translation_unit: TranslationUnit<'gc>,
namespace_index: Index<AbcNamespace>, namespace_index: Index<AbcNamespace>,
context: &mut GcContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,
) -> Result<Self, Error<'gc>> { ) -> Result<Self, Error<'gc>> {
if namespace_index.0 == 0 { if namespace_index.0 == 0 {
return Ok(Self::any(context.gc_context)); return Ok(Self::any(context.gc_context));
@ -61,29 +85,79 @@ impl<'gc> Namespace<'gc> {
.namespaces .namespaces
.get(actual_index) .get(actual_index)
.ok_or_else(|| format!("Unknown namespace constant {}", namespace_index.0).into()); .ok_or_else(|| format!("Unknown namespace constant {}", namespace_index.0).into());
let abc_namespace = abc_namespace?;
let ns = match abc_namespace? { let index = match abc_namespace {
AbcNamespace::Namespace(idx) => { AbcNamespace::Namespace(idx)
NamespaceData::Namespace(translation_unit.pool_string(idx.0, context)?) | AbcNamespace::Package(idx)
| AbcNamespace::PackageInternal(idx)
| AbcNamespace::Protected(idx)
| AbcNamespace::Explicit(idx)
| AbcNamespace::StaticProtected(idx)
| AbcNamespace::Private(idx) => idx,
};
let mut namespace_name = translation_unit.pool_string(index.0, &mut context.borrow_gc())?;
// Private namespaces don't get any of the namespace version checks
if let AbcNamespace::Private(_) = abc_namespace {
return Ok(Self(Gc::new(
context.gc_context,
NamespaceData::Private(namespace_name),
)));
}
// FIXME - what other versioned urls are there?
let is_versioned_url = |url: AvmAtom<'gc>| url.as_wstr().is_empty();
let is_public = matches!(
abc_namespace,
AbcNamespace::Namespace(_) | AbcNamespace::Package(_)
);
let api_version = if index.0 != 0 {
let is_playerglobals = translation_unit
.domain()
.is_playerglobals_domain(context.avm2);
let mut api_version = ApiVersion::AllVersions;
let stripped = strip_version_mark(namespace_name.as_wstr());
let has_version_mark = stripped.is_some();
if let Some((stripped, version)) = stripped {
assert!(
is_playerglobals,
"Found versioned url {namespace_name:?} in non-playerglobals domain"
);
let stripped_string = AvmString::new(context.gc_context, stripped);
namespace_name = context.interner.intern(context.gc_context, stripped_string);
api_version = version;
} }
AbcNamespace::Package(idx) => {
NamespaceData::Namespace(translation_unit.pool_string(idx.0, context)?) if is_playerglobals {
} if !has_version_mark && is_public && is_versioned_url(namespace_name) {
AbcNamespace::PackageInternal(idx) => { api_version = ApiVersion::VM_INTERNAL;
NamespaceData::PackageInternal(translation_unit.pool_string(idx.0, context)?) }
} } else if is_public {
AbcNamespace::Protected(idx) => { api_version = translation_unit.api_version(context.avm2);
NamespaceData::Protected(translation_unit.pool_string(idx.0, context)?) };
} api_version
AbcNamespace::Explicit(idx) => { } else {
NamespaceData::Explicit(translation_unit.pool_string(idx.0, context)?) // Note - avmplus walks the (user) call stack to determine the API version.
} // However, Flash Player appears to always use the root SWF api version
AbcNamespace::StaticProtected(idx) => { // for all swfs (e.g. those loaded through `Loader`). We can simply our code
NamespaceData::StaticProtected(translation_unit.pool_string(idx.0, context)?) // by skipping walking the stack, and just using the API version of our root SWF.
} context.avm2.root_api_version
AbcNamespace::Private(idx) => { };
NamespaceData::Private(translation_unit.pool_string(idx.0, context)?)
let ns = match abc_namespace {
AbcNamespace::Namespace(_) | AbcNamespace::Package(_) => {
NamespaceData::Namespace(namespace_name, api_version)
} }
AbcNamespace::PackageInternal(_) => NamespaceData::PackageInternal(namespace_name),
AbcNamespace::Protected(_) => NamespaceData::Protected(namespace_name),
AbcNamespace::Explicit(_) => NamespaceData::Explicit(namespace_name),
AbcNamespace::StaticProtected(_) => NamespaceData::StaticProtected(namespace_name),
AbcNamespace::Private(_) => unreachable!(),
}; };
Ok(Self(Gc::new(context.gc_context, ns))) Ok(Self(Gc::new(context.gc_context, ns)))
} }
@ -95,12 +169,16 @@ impl<'gc> Namespace<'gc> {
// TODO(moulins): allow passing an AvmAtom or a non-static `&WStr` directly // TODO(moulins): allow passing an AvmAtom or a non-static `&WStr` directly
pub fn package( pub fn package(
package_name: impl Into<AvmString<'gc>>, package_name: impl Into<AvmString<'gc>>,
api_version: ApiVersion,
context: &mut GcContext<'_, 'gc>, context: &mut GcContext<'_, 'gc>,
) -> Self { ) -> Self {
let atom = context let atom = context
.interner .interner
.intern(context.gc_context, package_name.into()); .intern(context.gc_context, package_name.into());
Self(Gc::new(context.gc_context, NamespaceData::Namespace(atom))) Self(Gc::new(
context.gc_context,
NamespaceData::Namespace(atom, api_version),
))
} }
// TODO(moulins): allow passing an AvmAtom or a non-static `&WStr` directly // TODO(moulins): allow passing an AvmAtom or a non-static `&WStr` directly
@ -118,11 +196,11 @@ impl<'gc> Namespace<'gc> {
} }
pub fn is_public(&self) -> bool { pub fn is_public(&self) -> bool {
matches!(*self.0, NamespaceData::Namespace(name) if name.as_wstr().is_empty()) matches!(*self.0, NamespaceData::Namespace(name, _) if name.as_wstr().is_empty())
} }
pub fn is_public_ignoring_ns(&self) -> bool { pub fn is_public_ignoring_ns(&self) -> bool {
matches!(*self.0, NamespaceData::Namespace(_)) matches!(*self.0, NamespaceData::Namespace(_, _))
} }
pub fn is_any(&self) -> bool { pub fn is_any(&self) -> bool {
@ -134,12 +212,12 @@ impl<'gc> Namespace<'gc> {
} }
pub fn is_namespace(&self) -> bool { pub fn is_namespace(&self) -> bool {
matches!(*self.0, NamespaceData::Namespace(_)) matches!(*self.0, NamespaceData::Namespace(_, _))
} }
pub fn as_uri_opt(&self) -> Option<AvmString<'gc>> { pub fn as_uri_opt(&self) -> Option<AvmString<'gc>> {
match *self.0 { match *self.0 {
NamespaceData::Namespace(a) => Some(a.into()), NamespaceData::Namespace(a, _) => Some(a.into()),
NamespaceData::PackageInternal(a) => Some(a.into()), NamespaceData::PackageInternal(a) => Some(a.into()),
NamespaceData::Protected(a) => Some(a.into()), NamespaceData::Protected(a) => Some(a.into()),
NamespaceData::Explicit(a) => Some(a.into()), NamespaceData::Explicit(a) => Some(a.into()),
@ -155,4 +233,49 @@ impl<'gc> Namespace<'gc> {
pub fn as_uri(&self) -> AvmString<'gc> { pub fn as_uri(&self) -> AvmString<'gc> {
self.as_uri_opt().unwrap_or_else(|| "".into()) self.as_uri_opt().unwrap_or_else(|| "".into())
} }
/// Compares two namespaces, requiring that their versions match exactly.
/// Normally, you should use `matches_ns,` which takes version compatibility
/// into account.
///
/// Namespace does not implement `PartialEq`, so that each caller is required
/// to explicitly choose either `exact_version_match` or `matches_ns`.
pub fn exact_version_match(&self, other: Self) -> bool {
if Gc::as_ptr(self.0) == Gc::as_ptr(other.0) {
true
} else if self.is_private() || other.is_private() {
false
} else {
*self.0 == *other.0
}
}
/// Compares this namespace to another, considering them equal if this namespace's version
/// is less than or equal to the other (definitions in this namespace version can be
/// seen by the other). This is used to implement `ProperyMap`, where we want to
/// a definition with `ApiVersion::SWF_16` to be visible when queried from
/// a SWF with `ApiVersion::SWF_16` or any higher version.
pub fn matches_ns(&self, other: Self) -> bool {
if self.exact_version_match(other) {
return true;
}
match (&*self.0, &*other.0) {
(
NamespaceData::Namespace(name1, version1),
NamespaceData::Namespace(name2, version2),
) => {
let name_matches = name1 == name2;
let version_matches = version1 <= version2;
if name_matches && !version_matches {
tracing::info!(
"Rejecting namespace match due to versions: {:?} {:?}",
self.0,
other.0
);
}
name_matches && version_matches
}
_ => false,
}
}
} }

View File

@ -271,7 +271,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
self.get_property( self.get_property(
&Multiname::new(activation.avm2().public_namespace, name), &Multiname::new(activation.avm2().find_public_namespace(), name),
activation, activation,
) )
} }
@ -301,7 +301,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
value: Value<'gc>, value: Value<'gc>,
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> { ) -> Result<(), Error<'gc>> {
let name = Multiname::new(activation.avm2().public_namespace, name); let name = Multiname::new(activation.avm2().public_namespace_base_version, name);
self.set_property_local(&name, value, activation) self.set_property_local(&name, value, activation)
} }
@ -368,7 +368,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> { ) -> Result<(), Error<'gc>> {
self.set_property( self.set_property(
&Multiname::new(activation.avm2().public_namespace, name), &Multiname::new(activation.avm2().public_namespace_base_version, name),
value, value,
activation, activation,
) )
@ -552,7 +552,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
self.call_property( self.call_property(
&Multiname::new(activation.avm2().public_namespace, name), &Multiname::new(activation.avm2().find_public_namespace(), name),
arguments, arguments,
activation, activation,
) )
@ -643,7 +643,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
name: impl Into<AvmString<'gc>>, name: impl Into<AvmString<'gc>>,
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> bool { ) -> bool {
self.has_property(&Multiname::new(activation.avm2().public_namespace, name)) self.has_property(&Multiname::new(
activation.avm2().find_public_namespace(),
name,
))
} }
/// Indicates whether or not a property or trait exists on an object and is /// Indicates whether or not a property or trait exists on an object and is
@ -658,7 +661,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
name: impl Into<AvmString<'gc>>, name: impl Into<AvmString<'gc>>,
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Result<bool, Error<'gc>> { ) -> Result<bool, Error<'gc>> {
Ok(self.has_own_property(&Multiname::new(activation.avm2().public_namespace, name))) Ok(self.has_own_property(&Multiname::new(
activation.avm2().find_public_namespace(),
name,
)))
} }
/// Returns true if an object has one or more traits of a given name. /// Returns true if an object has one or more traits of a given name.
@ -729,7 +735,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
name: impl Into<AvmString<'gc>>, name: impl Into<AvmString<'gc>>,
) -> Result<bool, Error<'gc>> { ) -> Result<bool, Error<'gc>> {
let name = Multiname::new(activation.avm2().public_namespace, name); let name = Multiname::new(activation.avm2().public_namespace_base_version, name);
self.delete_property(activation, &name) self.delete_property(activation, &name)
} }

View File

@ -377,7 +377,7 @@ impl<'gc> ClassObject<'gc> {
for interface_trait in iface_read.instance_traits() { for interface_trait in iface_read.instance_traits() {
if !interface_trait.name().namespace().is_public() { if !interface_trait.name().namespace().is_public() {
let public_name = QName::new( let public_name = QName::new(
activation.context.avm2.public_namespace, activation.context.avm2.public_namespace_vm_internal,
interface_trait.name().local_name(), interface_trait.name().local_name(),
); );
self.instance_vtable().copy_property_for_interface( self.instance_vtable().copy_property_for_interface(

View File

@ -21,7 +21,7 @@ pub fn namespace_allocator<'gc>(
activation.context.gc_context, activation.context.gc_context,
NamespaceObjectData { NamespaceObjectData {
base, base,
namespace: activation.context.avm2.public_namespace, namespace: activation.context.avm2.public_namespace_base_version,
}, },
)) ))
.into()) .into())

View File

@ -1,4 +1,5 @@
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::api_version::ApiVersion;
use crate::avm2::e4x::{E4XNode, E4XNodeKind}; use crate::avm2::e4x::{E4XNode, E4XNodeKind};
use crate::avm2::error::make_error_1089; use crate::avm2::error::make_error_1089;
use crate::avm2::object::script_object::ScriptObjectData; use crate::avm2::object::script_object::ScriptObjectData;
@ -173,8 +174,12 @@ impl<'gc> XmlListObject<'gc> {
if !matches!(*last_node.kind(), E4XNodeKind::ProcessingInstruction(_)) { if !matches!(*last_node.kind(), E4XNodeKind::ProcessingInstruction(_)) {
if let Some(name) = last_node.local_name() { if let Some(name) = last_node.local_name() {
let ns = match last_node.namespace() { let ns = match last_node.namespace() {
Some(ns) => Namespace::package(ns, &mut activation.context.borrow_gc()), Some(ns) => Namespace::package(
None => activation.avm2().public_namespace, ns,
ApiVersion::AllVersions,
&mut activation.context.borrow_gc(),
),
None => activation.avm2().public_namespace_base_version,
}; };
write.target_property = Some(Multiname::new(ns, name)); write.target_property = Some(Multiname::new(ns, name));
@ -812,7 +817,7 @@ impl<'gc> TObject<'gc> for XmlListObject<'gc> {
// FIXME: We probably need to take the namespace too. // FIXME: We probably need to take the namespace too.
// 2.e.i. Let z = ToAttributeName(x[i].[[Name]]) // 2.e.i. Let z = ToAttributeName(x[i].[[Name]])
let z = Multiname::attribute( let z = Multiname::attribute(
activation.avm2().public_namespace, activation.avm2().public_namespace_base_version,
child.local_name().expect("Attribute should have a name"), child.local_name().expect("Attribute should have a name"),
); );
// 2.e.ii. Call the [[Put]] method of x[i].[[Parent]] with arguments z and V // 2.e.ii. Call the [[Put]] method of x[i].[[Parent]] with arguments z and V

View File

@ -1,6 +1,7 @@
//! Object representation for XML objects //! Object representation for XML objects
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::api_version::ApiVersion;
use crate::avm2::e4x::{string_to_multiname, E4XNode, E4XNodeKind}; use crate::avm2::e4x::{string_to_multiname, E4XNode, E4XNodeKind};
use crate::avm2::error::make_error_1087; use crate::avm2::error::make_error_1087;
use crate::avm2::object::script_object::ScriptObjectData; use crate::avm2::object::script_object::ScriptObjectData;
@ -127,8 +128,12 @@ impl<'gc> XmlObject<'gc> {
pub fn namespace(&self, activation: &mut Activation<'_, 'gc>) -> Namespace<'gc> { pub fn namespace(&self, activation: &mut Activation<'_, 'gc>) -> Namespace<'gc> {
match self.0.read().node.namespace() { match self.0.read().node.namespace() {
Some(ns) => Namespace::package(ns, &mut activation.context.borrow_gc()), Some(ns) => Namespace::package(
None => activation.avm2().public_namespace, ns,
ApiVersion::AllVersions,
&mut activation.context.borrow_gc(),
),
None => activation.avm2().public_namespace_base_version,
} }
} }

View File

@ -59,7 +59,7 @@ impl<'gc, V> PropertyMap<'gc, V> {
pub fn get(&self, name: QName<'gc>) -> Option<&V> { pub fn get(&self, name: QName<'gc>) -> Option<&V> {
self.0.get(&name.local_name()).iter().find_map(|v| { self.0.get(&name.local_name()).iter().find_map(|v| {
v.iter() v.iter()
.filter(|(n, _)| *n == name.namespace()) .filter(|(n, _)| n.matches_ns(name.namespace()))
.map(|(_, v)| v) .map(|(_, v)| v)
.next() .next()
}) })
@ -72,7 +72,7 @@ impl<'gc, V> PropertyMap<'gc, V> {
if let Some(local_name) = name.local_name() { if let Some(local_name) = name.local_name() {
self.0.get(&local_name).iter().find_map(|v| { self.0.get(&local_name).iter().find_map(|v| {
v.iter() v.iter()
.filter(|(n, _)| name.namespace_set().iter().any(|ns| *ns == *n)) .filter(|(n, _)| name.namespace_set().iter().any(|ns| n.matches_ns(*ns)))
.map(|(_, v)| v) .map(|(_, v)| v)
.next() .next()
}) })
@ -88,7 +88,7 @@ impl<'gc, V> PropertyMap<'gc, V> {
if let Some(local_name) = name.local_name() { if let Some(local_name) = name.local_name() {
self.0.get(&local_name).iter().find_map(|v| { self.0.get(&local_name).iter().find_map(|v| {
v.iter() v.iter()
.filter(|(n, _)| name.namespace_set().iter().any(|ns| *ns == *n)) .filter(|(n, _)| name.namespace_set().iter().any(|ns| n.matches_ns(*ns)))
.map(|(ns, v)| (*ns, v)) .map(|(ns, v)| (*ns, v))
.next() .next()
}) })
@ -99,7 +99,10 @@ impl<'gc, V> PropertyMap<'gc, V> {
pub fn get_mut(&mut self, name: QName<'gc>) -> Option<&mut V> { pub fn get_mut(&mut self, name: QName<'gc>) -> Option<&mut V> {
if let Some(bucket) = self.0.get_mut(&name.local_name()) { if let Some(bucket) = self.0.get_mut(&name.local_name()) {
if let Some((_, old_value)) = bucket.iter_mut().find(|(n, _)| *n == name.namespace()) { if let Some((_, old_value)) = bucket
.iter_mut()
.find(|(n, _)| n.matches_ns(name.namespace()))
{
return Some(old_value); return Some(old_value);
} }
} }
@ -111,7 +114,7 @@ impl<'gc, V> PropertyMap<'gc, V> {
self.0 self.0
.get(&name.local_name()) .get(&name.local_name())
.iter() .iter()
.any(|v| v.iter().any(|(n, _)| *n == name.namespace())) .any(|v| v.iter().any(|(n, _)| n.matches_ns(name.namespace())))
} }
pub fn iter(&self) -> impl Iterator<Item = (AvmString<'gc>, Namespace<'gc>, &V)> { pub fn iter(&self) -> impl Iterator<Item = (AvmString<'gc>, Namespace<'gc>, &V)> {
@ -123,7 +126,10 @@ impl<'gc, V> PropertyMap<'gc, V> {
pub fn insert(&mut self, name: QName<'gc>, mut value: V) -> Option<V> { pub fn insert(&mut self, name: QName<'gc>, mut value: V) -> Option<V> {
let bucket = self.0.entry(name.local_name()).or_default(); let bucket = self.0.entry(name.local_name()).or_default();
if let Some((_, old_value)) = bucket.iter_mut().find(|(n, _)| *n == name.namespace()) { if let Some((_, old_value)) = bucket
.iter_mut()
.find(|(n, _)| n.matches_ns(name.namespace()))
{
swap(old_value, &mut value); swap(old_value, &mut value);
Some(value) Some(value)
@ -142,7 +148,7 @@ impl<'gc, V> PropertyMap<'gc, V> {
) -> Option<V> { ) -> Option<V> {
let bucket = self.0.entry(name).or_default(); let bucket = self.0.entry(name).or_default();
if let Some((_, old_value)) = bucket.iter_mut().find(|(n, _)| *n == ns) { if let Some((_, old_value)) = bucket.iter_mut().find(|(n, _)| n.matches_ns(ns)) {
swap(old_value, &mut value); swap(old_value, &mut value);
Some(value) Some(value)
@ -161,7 +167,7 @@ impl<'gc, V> PropertyMap<'gc, V> {
let position = bucket let position = bucket
.iter_mut() .iter_mut()
.enumerate() .enumerate()
.find(|(_, (n, _))| *n == name.namespace()); .find(|(_, (n, _))| n.matches_ns(name.namespace()));
if let Some((position, _)) = position { if let Some((position, _)) = position {
return Some(bucket.remove(position).1); return Some(bucket.remove(position).1);
} }

View File

@ -1,12 +1,15 @@
use crate::avm2::script::TranslationUnit; use crate::avm2::script::TranslationUnit;
use crate::avm2::{Activation, Error, Namespace}; use crate::avm2::{Activation, Error, Namespace};
use crate::context::GcContext; use crate::context::UpdateContext;
use crate::either::Either; use crate::either::Either;
use crate::string::{AvmString, WStr, WString}; use crate::string::{AvmString, WStr, WString};
use gc_arena::{Collect, Mutation}; use gc_arena::{Collect, Mutation};
use std::fmt::Debug; use std::fmt::Debug;
use swf::avm2::types::{Index, Multiname as AbcMultiname}; use swf::avm2::types::{Index, Multiname as AbcMultiname};
use super::api_version::ApiVersion;
use super::Multiname;
/// A `QName`, likely "qualified name", consists of a namespace and name string. /// A `QName`, likely "qualified name", consists of a namespace and name string.
/// ///
/// This is technically interchangeable with `xml::XMLName`, as they both /// This is technically interchangeable with `xml::XMLName`, as they both
@ -26,7 +29,7 @@ pub struct QName<'gc> {
impl<'gc> PartialEq for QName<'gc> { impl<'gc> PartialEq for QName<'gc> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// Implemented by hand to enforce order of comparisons for perf // Implemented by hand to enforce order of comparisons for perf
self.name == other.name && self.ns == other.ns self.name == other.name && self.ns.exact_version_match(other.ns)
} }
} }
@ -47,7 +50,7 @@ impl<'gc> QName<'gc> {
pub fn from_abc_multiname( pub fn from_abc_multiname(
translation_unit: TranslationUnit<'gc>, translation_unit: TranslationUnit<'gc>,
multiname_index: Index<AbcMultiname>, multiname_index: Index<AbcMultiname>,
context: &mut GcContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,
) -> Result<Self, Error<'gc>> { ) -> Result<Self, Error<'gc>> {
if multiname_index.0 == 0 { if multiname_index.0 == 0 {
return Err("Attempted to load a trait name of index zero".into()); return Err("Attempted to load a trait name of index zero".into());
@ -64,8 +67,29 @@ impl<'gc> QName<'gc> {
Ok(match abc_multiname? { Ok(match abc_multiname? {
AbcMultiname::QName { namespace, name } => Self { AbcMultiname::QName { namespace, name } => Self {
ns: translation_unit.pool_namespace(*namespace, context)?, ns: translation_unit.pool_namespace(*namespace, context)?,
name: translation_unit.pool_string(name.0, context)?.into(), name: translation_unit
.pool_string(name.0, &mut context.borrow_gc())?
.into(),
}, },
AbcMultiname::Multiname {
namespace_set,
name,
} => {
let ns_set =
Multiname::abc_namespace_set(translation_unit, *namespace_set, context)?;
if ns_set.len() == 1 {
Self {
ns: ns_set.get(0).unwrap(),
name: translation_unit
.pool_string(name.0, &mut context.borrow_gc())?
.into(),
}
} else {
return Err(
"Attempted to pull QName from multiname with multiple namespaces".into(),
);
}
}
_ => return Err("Attempted to pull QName from non-QName multiname".into()), _ => return Err("Attempted to pull QName from non-QName multiname".into()),
}) })
} }
@ -78,24 +102,28 @@ impl<'gc> QName<'gc> {
/// LOCAL_NAME (Use the public namespace) /// LOCAL_NAME (Use the public namespace)
/// ///
/// This does *not* handle `Vector.<SomeTypeParam>` - use `get_defined_value_handling_vector` for that /// This does *not* handle `Vector.<SomeTypeParam>` - use `get_defined_value_handling_vector` for that
pub fn from_qualified_name(name: AvmString<'gc>, activation: &mut Activation<'_, 'gc>) -> Self { pub fn from_qualified_name(
name: AvmString<'gc>,
api_version: ApiVersion,
activation: &mut Activation<'_, 'gc>,
) -> Self {
let parts = name let parts = name
.rsplit_once(WStr::from_units(b"::")) .rsplit_once(WStr::from_units(b"::"))
.or_else(|| name.rsplit_once(WStr::from_units(b"."))); .or_else(|| name.rsplit_once(WStr::from_units(b".")));
let mut context = activation.borrow_gc();
if let Some((package_name, local_name)) = parts { if let Some((package_name, local_name)) = parts {
let mut context = activation.borrow_gc();
let package_name = context let package_name = context
.interner .interner
.intern_wstr(context.gc_context, package_name); .intern_wstr(context.gc_context, package_name);
Self { Self {
ns: Namespace::package(package_name, &mut context), ns: Namespace::package(package_name, api_version, &mut context),
name: AvmString::new(context.gc_context, local_name), name: AvmString::new(context.gc_context, local_name),
} }
} else { } else {
Self { Self {
ns: activation.avm2().public_namespace, ns: Namespace::package("", api_version, &mut context),
name, name,
} }
} }

View File

@ -1,5 +1,6 @@
//! Whole script representation //! Whole script representation
use super::api_version::ApiVersion;
use super::traits::TraitKind; use super::traits::TraitKind;
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::class::Class; use crate::avm2::class::Class;
@ -131,6 +132,15 @@ impl<'gc> TranslationUnit<'gc> {
self.0.read().movie.clone() self.0.read().movie.clone()
} }
pub fn api_version(self, avm2: &Avm2<'gc>) -> ApiVersion {
if self.domain().is_playerglobals_domain(avm2) {
// FIXME: get this from the player version we're emulating
ApiVersion::SWF_31
} else {
avm2.root_api_version
}
}
/// Load a method from the ABC file and return its method definition. /// Load a method from the ABC file and return its method definition.
pub fn load_method( pub fn load_method(
self, self,
@ -143,7 +153,7 @@ impl<'gc> TranslationUnit<'gc> {
return Ok(*method); return Ok(*method);
} }
let is_global = read.domain.is_playerglobals_domain(activation); let is_global = read.domain.is_playerglobals_domain(activation.avm2());
drop(read); drop(read);
let bc_method = let bc_method =
@ -297,7 +307,7 @@ impl<'gc> TranslationUnit<'gc> {
pub fn pool_namespace( pub fn pool_namespace(
self, self,
ns_index: Index<AbcNamespace>, ns_index: Index<AbcNamespace>,
context: &mut GcContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,
) -> Result<Namespace<'gc>, Error<'gc>> { ) -> Result<Namespace<'gc>, Error<'gc>> {
let read = self.0.read(); let read = self.0.read();
if let Some(Some(namespace)) = read.namespaces.get(ns_index.0 as usize) { if let Some(Some(namespace)) = read.namespaces.get(ns_index.0 as usize) {
@ -317,7 +327,7 @@ impl<'gc> TranslationUnit<'gc> {
pub fn pool_maybe_uninitialized_multiname( pub fn pool_maybe_uninitialized_multiname(
self, self,
multiname_index: Index<AbcMultiname>, multiname_index: Index<AbcMultiname>,
context: &mut GcContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> { ) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
let mc = context.gc_context; let mc = context.gc_context;
let read = self.0.read(); let read = self.0.read();
@ -341,7 +351,7 @@ impl<'gc> TranslationUnit<'gc> {
pub fn pool_multiname_static( pub fn pool_multiname_static(
self, self,
multiname_index: Index<AbcMultiname>, multiname_index: Index<AbcMultiname>,
context: &mut GcContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> { ) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
let multiname = self.pool_maybe_uninitialized_multiname(multiname_index, context)?; let multiname = self.pool_maybe_uninitialized_multiname(multiname_index, context)?;
if multiname.has_lazy_component() { if multiname.has_lazy_component() {
@ -358,7 +368,7 @@ impl<'gc> TranslationUnit<'gc> {
pub fn pool_multiname_static_any( pub fn pool_multiname_static_any(
self, self,
multiname_index: Index<AbcMultiname>, multiname_index: Index<AbcMultiname>,
context: &mut GcContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> { ) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
if multiname_index.0 == 0 { if multiname_index.0 == 0 {
let mc = context.gc_context; let mc = context.gc_context;
@ -503,7 +513,7 @@ impl<'gc> Script<'gc> {
if let TraitKind::Class { class, .. } = newtrait.kind() { if let TraitKind::Class { class, .. } = newtrait.kind() {
write write
.domain .domain
.export_class(*class, activation.context.gc_context); .export_class(newtrait.name(), *class, activation.context.gc_context);
} }
write.traits.push(newtrait); write.traits.push(newtrait);

View File

@ -202,7 +202,7 @@ impl<'gc> Trait<'gc> {
abc_trait: &AbcTrait, abc_trait: &AbcTrait,
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Result<Self, Error<'gc>> { ) -> Result<Self, Error<'gc>> {
let name = QName::from_abc_multiname(unit, abc_trait.name, &mut activation.borrow_gc())?; let name = QName::from_abc_multiname(unit, abc_trait.name, &mut activation.context)?;
Ok(match &abc_trait.kind { Ok(match &abc_trait.kind {
AbcTraitKind::Slot { AbcTraitKind::Slot {
@ -211,7 +211,7 @@ impl<'gc> Trait<'gc> {
value, value,
} => { } => {
let type_name = unit let type_name = unit
.pool_multiname_static_any(*type_name, &mut activation.borrow_gc())? .pool_multiname_static_any(*type_name, &mut activation.context)?
.deref() .deref()
.clone(); .clone();
let default_value = slot_default_value(unit, value, &type_name, activation)?; let default_value = slot_default_value(unit, value, &type_name, activation)?;
@ -278,7 +278,7 @@ impl<'gc> Trait<'gc> {
value, value,
} => { } => {
let type_name = unit let type_name = unit
.pool_multiname_static_any(*type_name, &mut activation.borrow_gc())? .pool_multiname_static_any(*type_name, &mut activation.context)?
.deref() .deref()
.clone(); .clone();
let default_value = slot_default_value(unit, value, &type_name, activation)?; let default_value = slot_default_value(unit, value, &type_name, activation)?;

View File

@ -535,7 +535,7 @@ pub fn abc_default_value<'gc>(
| AbcDefaultValue::Explicit(ns) | AbcDefaultValue::Explicit(ns)
| AbcDefaultValue::StaticProtected(ns) | AbcDefaultValue::StaticProtected(ns)
| AbcDefaultValue::Private(ns) => { | AbcDefaultValue::Private(ns) => {
let ns = translation_unit.pool_namespace(*ns, &mut activation.borrow_gc())?; let ns = translation_unit.pool_namespace(*ns, &mut activation.context)?;
NamespaceObject::from_namespace(activation, ns).map(Into::into) NamespaceObject::from_namespace(activation, ns).map(Into::into)
} }
} }

View File

@ -296,7 +296,7 @@ impl<'gc> VTable<'gc> {
// but with this class's protected namespace // but with this class's protected namespace
for (local_name, ns, prop) in superclass_vtable.0.read().resolved_traits.iter() for (local_name, ns, prop) in superclass_vtable.0.read().resolved_traits.iter()
{ {
if ns == super_protected_namespace { if ns.exact_version_match(super_protected_namespace) {
let new_name = QName::new(protected_namespace, local_name); let new_name = QName::new(protected_namespace, local_name);
write.resolved_traits.insert(new_name, *prop); write.resolved_traits.insert(new_name, *prop);
} }

View File

@ -1885,8 +1885,10 @@ pub trait TDisplayObject<'gc>:
.avm2_domain(); .avm2_domain();
let mut activation = let mut activation =
Avm2Activation::from_domain(context.reborrow(), domain); Avm2Activation::from_domain(context.reborrow(), domain);
let name = let name = Avm2Multiname::new(
Avm2Multiname::new(activation.avm2().public_namespace, self.name()); activation.avm2().find_public_namespace(),
self.name(),
);
if let Err(e) = p.init_property(&name, c.into(), &mut activation) { if let Err(e) = p.init_property(&name, c.into(), &mut activation) {
tracing::error!( tracing::error!(
"Got error when setting AVM2 child named \"{}\": {}", "Got error when setting AVM2 child named \"{}\": {}",

View File

@ -2,8 +2,8 @@
use crate::avm1::{Activation, ActivationIdentifier, TObject}; use crate::avm1::{Activation, ActivationIdentifier, TObject};
use crate::avm2::{ use crate::avm2::{
Activation as Avm2Activation, Avm2, EventObject as Avm2EventObject, TObject as _, Activation as Avm2Activation, Avm2, EventObject as Avm2EventObject, Multiname as Avm2Multiname,
Value as Avm2Value, TObject as _, Value as Avm2Value,
}; };
use crate::context::{RenderContext, UpdateContext}; use crate::context::{RenderContext, UpdateContext};
use crate::display_object::avm1_button::Avm1Button; use crate::display_object::avm1_button::Avm1Button;
@ -653,13 +653,16 @@ impl<'gc> ChildContainer<'gc> {
if child.has_explicit_name() { if child.has_explicit_name() {
if let Avm2Value::Object(parent_obj) = parent.object2() { if let Avm2Value::Object(parent_obj) = parent.object2() {
let mut activation = Avm2Activation::from_nothing(context.reborrow()); let mut activation = Avm2Activation::from_nothing(context.reborrow());
let current_val = let name = Avm2Multiname::new(
parent_obj.get_public_property(child.name(), &mut activation); activation.avm2().find_public_namespace(),
child.name(),
);
let current_val = parent_obj.get_property(&name, &mut activation);
match current_val { match current_val {
Ok(Avm2Value::Null) | Ok(Avm2Value::Undefined) => {} Ok(Avm2Value::Null) | Ok(Avm2Value::Undefined) => {}
Ok(_other) => { Ok(_other) => {
let res = parent_obj.set_public_property( let res = parent_obj.set_property(
child.name(), &name,
Avm2Value::Null, Avm2Value::Null,
&mut activation, &mut activation,
); );

View File

@ -856,7 +856,11 @@ impl<'gc> MovieClip<'gc> {
reader.read_str()?.decode(reader.encoding()), reader.read_str()?.decode(reader.encoding()),
); );
let name = Avm2QName::from_qualified_name(class_name, &mut activation); let name = Avm2QName::from_qualified_name(
class_name,
activation.avm2().root_api_version,
&mut activation,
);
let library = activation let library = activation
.context .context
.library .library

View File

@ -6,6 +6,7 @@ use crate::avm1::SystemProperties;
use crate::avm1::VariableDumper; use crate::avm1::VariableDumper;
use crate::avm1::{Activation, ActivationIdentifier}; use crate::avm1::{Activation, ActivationIdentifier};
use crate::avm1::{ScriptObject, TObject, Value}; use crate::avm1::{ScriptObject, TObject, Value};
use crate::avm2::api_version::ApiVersion;
use crate::avm2::{ use crate::avm2::{
object::LoaderInfoObject, object::TObject as _, Activation as Avm2Activation, Avm2, CallStack, object::LoaderInfoObject, object::TObject as _, Activation as Avm2Activation, Avm2, CallStack,
Object as Avm2Object, Object as Avm2Object,
@ -383,6 +384,11 @@ impl Player {
self.instance_counter = 0; self.instance_counter = 0;
self.mutate_with_update_context(|context| { self.mutate_with_update_context(|context| {
if context.swf.is_action_script_3() {
context.avm2.root_api_version = ApiVersion::from_swf_version(context.swf.version())
.unwrap_or_else(|| panic!("Unknown SWF version {}", context.swf.version()));
}
context.stage.set_movie_size( context.stage.set_movie_size(
context.gc_context, context.gc_context,
context.swf.width().to_pixels() as u32, context.swf.width().to_pixels() as u32,

View File

@ -0,0 +1,31 @@
package com.ruffle2 {
import flash.display.MovieClip;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.Event;
public class Test extends MovieClip {
public function Test() {
var loader = new Loader();
var self = this;
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e) {
trace("Calling childPublicMethod on " + loader.content);
loader.content.childPublicMethod(self);
trace("Called childPublicMethod");
trace("Calling childAS3Method on " + loader.content);
loader.content.childAS3Method(self);
trace("Called childAS3Method");
})
loader.load(new URLRequest("newer/newer.swf"));
}
public function parentPublicMethod() {
trace("Called Test.parentPublicMethod");
}
AS3 function parentAS3Method() {
trace("Called Test.parentAS3Method()");
}
}
}

View File

@ -0,0 +1,28 @@
package com.ruffle {
import flash.display.MovieClip;
import flash.events.Event;
public class Newer extends MovieClip {
public function Newer() {
trace("Initialized Newer with parent: " + this.parent + " Event = " + Event["WORKER_STATE"]);
try {
this.gotoAndPlay("badFrame");
} catch (e) {
// FIXME - print the entire error when Ruffle matches Flash Player
trace("Caught error (truncated): " + e.toString().slice(0, 27));
}
}
public function childPublicMethod(target: Object) {
trace("Newer.childPublicMethod: Calling parentPublicMethod() on " + target);
target.parentPublicMethod();
trace("Newer.childPublicMethod: Called parentPublicMethod() on " + target);
}
AS3 function childAS3Method(target: Object) {
trace("Newer.childAS3Method: Calling parentAS3Method() on " + target);
target.parentAS3Method();
trace("Newer.childAS3Method: Called parentAS3Method() on " + target);
}
}
}

View File

@ -0,0 +1,12 @@
Initialized Newer with parent: null Event = undefined
Caught error (truncated): ArgumentError: Error #2109:
Calling childPublicMethod on [object Newer]
Newer.childPublicMethod: Calling parentPublicMethod() on [object Test]
Called Test.parentPublicMethod
Newer.childPublicMethod: Called parentPublicMethod() on [object Test]
Called childPublicMethod
Calling childAS3Method on [object Newer]
Newer.childAS3Method: Calling parentAS3Method() on [object Test]
Called Test.parentAS3Method()
Newer.childAS3Method: Called parentAS3Method() on [object Test]
Called childAS3Method

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_ticks = 2

View File

@ -0,0 +1,31 @@
package com.ruffle2 {
import flash.display.MovieClip;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.Event;
public class Test extends MovieClip {
public function Test() {
var loader = new Loader();
var self = this;
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e) {
trace("Calling childPublicMethod on " + loader.content);
loader.content.childPublicMethod(self);
trace("Called childPublicMethod");
trace("Calling childAS3Method on " + loader.content);
loader.content.childAS3Method(self);
trace("Called childAS3Method");
})
loader.load(new URLRequest("older/older.swf"));
}
public function parentPublicMethod() {
trace("Called Test.parentPublicMethod");
}
AS3 function parentAS3Method() {
trace("Called Test.parentAS3Method()");
}
}
}

View File

@ -0,0 +1,28 @@
package com.ruffle {
import flash.display.MovieClip;
import flash.events.Event;
public class Older extends MovieClip {
public function Older() {
trace("Initialized Older with parent: " + this.parent + " Event = " + Event["WORKER_STATE"]);
try {
this.gotoAndPlay("badFrame");
} catch (e) {
// FIXME - print the entire error when Ruffle matches Flash Player
trace("Caught error (truncated): " + e.toString().slice(0, 27));
}
}
public function childPublicMethod(target: Object) {
trace("Older.childPublicMethod: Calling parentPublicMethod() on " + target);
target.parentPublicMethod();
trace("Older.olderPublicMethod: Called parentPublicMethod() on " + target);
}
AS3 function childAS3Method(target: Object) {
trace("Older.childAS3Method: Calling parentAS3Method() on " + target);
target.parentAS3Method();
trace("Older.childAS3Method: Called parentAS3Method() on " + target);
}
}
}

View File

@ -0,0 +1,12 @@
Initialized Older with parent: null Event = workerState
Caught error (truncated): ArgumentError: Error #2109:
Calling childPublicMethod on [object Older]
Older.childPublicMethod: Calling parentPublicMethod() on [object Test]
Called Test.parentPublicMethod
Older.olderPublicMethod: Called parentPublicMethod() on [object Test]
Called childPublicMethod
Calling childAS3Method on [object Older]
Older.childAS3Method: Calling parentAS3Method() on [object Test]
Called Test.parentAS3Method()
Older.childAS3Method: Called parentAS3Method() on [object Test]
Called childAS3Method

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_ticks = 2

View File

@ -0,0 +1,15 @@
package {
import flash.display.MovieClip;
public class Test extends MovieClip {
public function Test() {
this.isPlaying;
this["isPlaying"];
}
public function get isPlaying():Boolean {
trace("Custom non-override isPlaying");
return false;
}
}
}

View File

@ -0,0 +1,2 @@
Custom non-override isPlaying
Custom non-override isPlaying

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_ticks = 1