Skip to content

Commit fa05c66

Browse files
Aaron1011Dinnerbone
authored andcommitted
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
1 parent 4d99459 commit fa05c66

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+880
-273
lines changed

core/build_playerglobal/src/lib.rs

+48-8
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ pub fn build_playerglobal(
5252
&asc_path.to_string_lossy(),
5353
"macromedia.asc.embedding.ScriptCompiler",
5454
"-optimize",
55+
"-builtin",
56+
"-apiversioning",
57+
"-version",
58+
"9",
5559
"-outdir",
5660
&out_dir.to_string_lossy(),
5761
"-out",
@@ -114,18 +118,54 @@ fn resolve_multiname_name<'a>(abc: &'a AbcFile, multiname: &Multiname) -> &'a st
114118
}
115119
}
116120

121+
// Strips off the version mark inserted by 'asc.jar',
122+
// giving us a valid Rust module name. The actual versioning logic
123+
// is handling in Ruffle when we load playerglobals
124+
fn strip_version_mark(val: &str) -> &str {
125+
const MIN_API_MARK: usize = 0xE000;
126+
const MAX_API_MARK: usize = 0xF8FF;
127+
128+
if let Some(chr) = val.chars().last() {
129+
if chr as usize >= MIN_API_MARK && chr as usize <= MAX_API_MARK {
130+
// The version mark is 3 bytes in utf-8
131+
return &val[..val.len() - 3];
132+
}
133+
}
134+
val
135+
}
136+
117137
// Like `resolve_multiname_name`, but for namespaces instead.
118138
fn resolve_multiname_ns<'a>(abc: &'a AbcFile, multiname: &Multiname) -> &'a str {
119-
if let Multiname::QName { namespace, .. } = multiname {
120-
let ns = &abc.constant_pool.namespaces[namespace.0 as usize - 1];
121-
if let Namespace::Package(p) | Namespace::PackageInternal(p) = ns {
122-
&abc.constant_pool.strings[p.0 as usize - 1]
123-
} else {
124-
panic!("Unexpected Namespace {ns:?}");
139+
let ns = match multiname {
140+
Multiname::QName { namespace, .. } => {
141+
&abc.constant_pool.namespaces[namespace.0 as usize - 1]
125142
}
143+
Multiname::Multiname { namespace_set, .. } => {
144+
if namespace_set.0 == 0 {
145+
panic!("Multiname namespace set must not be null");
146+
}
147+
148+
let actual_index = namespace_set.0 as usize - 1;
149+
let ns_set = abc
150+
.constant_pool
151+
.namespace_sets
152+
.get(actual_index)
153+
.unwrap_or_else(|| panic!("Unknown namespace set constant {}", actual_index));
154+
155+
if ns_set.len() == 1 {
156+
&abc.constant_pool.namespaces[ns_set[0].0 as usize - 1]
157+
} else {
158+
panic!("Found multiple namespaces in namespace set {ns_set:?}")
159+
}
160+
}
161+
_ => panic!("Unexpected Multiname {multiname:?}"),
162+
};
163+
let namespace = if let Namespace::Package(p) | Namespace::PackageInternal(p) = ns {
164+
&abc.constant_pool.strings[p.0 as usize - 1]
126165
} else {
127-
panic!("Unexpected Multiname {multiname:?}");
128-
}
166+
panic!("Unexpected Namespace {ns:?}");
167+
};
168+
strip_version_mark(namespace)
129169
}
130170

131171
fn flash_to_rust_path(path: &str) -> String {

core/src/avm2.rs

+46-4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ macro_rules! avm_debug {
2929

3030
pub mod activation;
3131
mod amf;
32+
pub mod api_version;
3233
mod array;
3334
pub mod bytearray;
3435
mod call_stack;
@@ -77,8 +78,10 @@ pub use crate::avm2::object::{
7778
pub use crate::avm2::qname::QName;
7879
pub use crate::avm2::value::Value;
7980

81+
use self::api_version::ApiVersion;
8082
use self::object::WeakObject;
8183
use self::scope::Scope;
84+
use num_traits::FromPrimitive;
8285

8386
const BROADCAST_WHITELIST: [&str; 4] = ["enterFrame", "exitFrame", "frameConstructed", "render"];
8487

@@ -113,7 +116,13 @@ pub struct Avm2<'gc> {
113116
/// However, it's not strictly defined which items end up there.
114117
toplevel_global_object: Option<Object<'gc>>,
115118

116-
pub public_namespace: Namespace<'gc>,
119+
/// The public namespace, versioned with `ApiVersion::ALL_VERSIONS`.
120+
/// When calling into user code, you should almost always use `find_public_namespace`
121+
/// instead, as it will return the correct version for the current call stack.
122+
public_namespace_base_version: Namespace<'gc>,
123+
// FIXME - make this an enum map once gc-arena supports it
124+
public_namespaces: Vec<Namespace<'gc>>,
125+
public_namespace_vm_internal: Namespace<'gc>,
117126
pub internal_namespace: Namespace<'gc>,
118127
pub as3_namespace: Namespace<'gc>,
119128
pub vector_public_namespace: Namespace<'gc>,
@@ -156,6 +165,14 @@ pub struct Avm2<'gc> {
156165
/// strong references around (this matches Flash's behavior).
157166
orphan_objects: Rc<Vec<DisplayObjectWeak<'gc>>>,
158167

168+
/// The api version of our root movie clip. Note - this is used as the
169+
/// api version for swfs loaded via `Loader`, overriding the api version
170+
/// specified in the loaded SWF. This is only used for API versioning (hiding
171+
/// definitions from playerglobals) - version-specific behavior in things like
172+
/// `gotoAndPlay` uses the current movie clip's SWF version.
173+
#[collect(require_static)]
174+
pub root_api_version: ApiVersion,
175+
159176
#[cfg(feature = "avm_debug")]
160177
pub debug_output: bool,
161178
}
@@ -167,6 +184,10 @@ impl<'gc> Avm2<'gc> {
167184
let stage_domain =
168185
Domain::uninitialized_domain(context.gc_context, Some(playerglobals_domain));
169186

187+
let public_namespaces = (0..=(ApiVersion::VM_INTERNAL as usize))
188+
.map(|val| Namespace::package("", ApiVersion::from_usize(val).unwrap(), context))
189+
.collect();
190+
170191
Self {
171192
player_version,
172193
stack: Vec::new(),
@@ -177,13 +198,24 @@ impl<'gc> Avm2<'gc> {
177198
system_classes: None,
178199
toplevel_global_object: None,
179200

180-
public_namespace: Namespace::package("", context),
201+
public_namespace_base_version: Namespace::package("", ApiVersion::AllVersions, context),
202+
public_namespaces,
203+
public_namespace_vm_internal: Namespace::package("", ApiVersion::VM_INTERNAL, context),
181204
internal_namespace: Namespace::internal("", context),
182-
as3_namespace: Namespace::package("http://adobe.com/AS3/2006/builtin", context),
183-
vector_public_namespace: Namespace::package("__AS3__.vec", context),
205+
as3_namespace: Namespace::package(
206+
"http://adobe.com/AS3/2006/builtin",
207+
ApiVersion::AllVersions,
208+
context,
209+
),
210+
vector_public_namespace: Namespace::package(
211+
"__AS3__.vec",
212+
ApiVersion::AllVersions,
213+
context,
214+
),
184215
vector_internal_namespace: Namespace::internal("__AS3__.vec", context),
185216
proxy_namespace: Namespace::package(
186217
"http://www.adobe.com/2006/actionscript/flash/proxy",
218+
ApiVersion::AllVersions,
187219
context,
188220
),
189221
// these are required to facilitate shared access between Rust and AS
@@ -201,6 +233,9 @@ impl<'gc> Avm2<'gc> {
201233

202234
orphan_objects: Default::default(),
203235

236+
// Set the lowest version for now - this be overriden when we set our movie
237+
root_api_version: ApiVersion::AllVersions,
238+
204239
#[cfg(feature = "avm_debug")]
205240
debug_output: false,
206241
}
@@ -639,6 +674,13 @@ impl<'gc> Avm2<'gc> {
639674

640675
#[cfg(not(feature = "avm_debug"))]
641676
pub const fn set_show_debug_output(&self, _visible: bool) {}
677+
678+
/// Gets the public namespace, versioned based on the current root SWF.
679+
/// See `AvmCore::findPublicNamespace()`
680+
/// https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/AvmCore.cpp#L5809C25-L5809C25
681+
pub fn find_public_namespace(&self) -> Namespace<'gc> {
682+
self.public_namespaces[self.root_api_version as usize]
683+
}
642684
}
643685

644686
/// If the provided `DisplayObjectWeak` should have frames run, returns

core/src/avm2/activation.rs

+8-11
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
761761
) -> Result<Namespace<'gc>, Error<'gc>> {
762762
method
763763
.translation_unit()
764-
.pool_namespace(index, &mut self.borrow_gc())
764+
.pool_namespace(index, &mut self.context)
765765
}
766766

767767
/// Retrieve a multiname from the current constant pool.
@@ -772,7 +772,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
772772
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
773773
method
774774
.translation_unit()
775-
.pool_maybe_uninitialized_multiname(index, &mut self.borrow_gc())
775+
.pool_maybe_uninitialized_multiname(index, &mut self.context)
776776
}
777777

778778
/// Retrieve a multiname from the current constant pool.
@@ -784,7 +784,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
784784
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
785785
let name = method
786786
.translation_unit()
787-
.pool_maybe_uninitialized_multiname(index, &mut self.borrow_gc())?;
787+
.pool_maybe_uninitialized_multiname(index, &mut self.context)?;
788788
if name.has_lazy_component() {
789789
let name = name.fill_with_runtime_params(self)?;
790790
Ok(Gc::new(self.context.gc_context, name))
@@ -804,7 +804,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
804804
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
805805
method
806806
.translation_unit()
807-
.pool_multiname_static(index, &mut self.borrow_gc())
807+
.pool_multiname_static(index, &mut self.context)
808808
}
809809

810810
/// Retrieve a static, or non-runtime, multiname from the current constant
@@ -818,7 +818,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
818818
) -> Result<Gc<'gc, Multiname<'gc>>, Error<'gc>> {
819819
method
820820
.translation_unit()
821-
.pool_multiname_static_any(index, &mut self.borrow_gc())
821+
.pool_multiname_static_any(index, &mut self.context)
822822
}
823823

824824
/// Retrieve a method entry from the current ABC file's method table.
@@ -1652,7 +1652,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
16521652
}
16531653

16541654
let name = name_value.coerce_to_string(self)?;
1655-
let multiname = Multiname::new(self.avm2().public_namespace, name);
1655+
let multiname = Multiname::new(self.avm2().find_public_namespace(), name);
16561656
let has_prop = obj.has_property_via_in(self, &multiname)?;
16571657

16581658
self.push_stack(has_prop);
@@ -1672,11 +1672,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
16721672
// for `finally` scopes, FP just creates a bare object.
16731673
self.avm2().classes().object.construct(self, &[])?
16741674
} else {
1675-
let qname = QName::from_abc_multiname(
1676-
method.translation_unit(),
1677-
vname,
1678-
&mut self.borrow_gc(),
1679-
)?;
1675+
let qname =
1676+
QName::from_abc_multiname(method.translation_unit(), vname, &mut self.context)?;
16801677
ScriptObject::catch_scope(self.context.gc_context, &qname)
16811678
};
16821679
self.push_stack(so);

core/src/avm2/api_version.rs

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

0 commit comments

Comments
 (0)