core: Reimplement `Object.registerClass`.
Maintain an external mapping from symbol names to registered constructors to properly handle `registerClass` being called on not-yet-available symbols. SWFs v6 and v7+ each have a separate global mapping, with different case sensitivities. Also returns the correct boolean value to the AVM. Fixes #2343 and #1864.
This commit is contained in:
parent
9bb36885bb
commit
f394953331
|
@ -5,7 +5,6 @@ use crate::avm1::function::{Executable, FunctionObject};
|
||||||
use crate::avm1::property::Attribute::{self, *};
|
use crate::avm1::property::Attribute::{self, *};
|
||||||
use crate::avm1::{Object, ScriptObject, TObject, Value};
|
use crate::avm1::{Object, ScriptObject, TObject, Value};
|
||||||
use crate::avm_warn;
|
use crate::avm_warn;
|
||||||
use crate::character::Character;
|
|
||||||
use crate::display_object::TDisplayObject;
|
use crate::display_object::TDisplayObject;
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
use gc_arena::MutationContext;
|
use gc_arena::MutationContext;
|
||||||
|
@ -147,36 +146,35 @@ pub fn register_class<'gc>(
|
||||||
_this: Object<'gc>,
|
_this: Object<'gc>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
if let Some(class_name) = args.get(0).cloned() {
|
let (class_name, constructor) = match args {
|
||||||
let class_name = class_name.coerce_to_string(activation)?;
|
[class_name, constructor, ..] => (class_name, constructor),
|
||||||
if let Some(movie) = activation.base_clip().movie() {
|
_ => return Ok(Value::Bool(false)),
|
||||||
if let Some(Character::MovieClip(movie_clip)) = activation
|
};
|
||||||
.context
|
|
||||||
.library
|
let constructor = match constructor {
|
||||||
.library_for_movie_mut(movie)
|
Value::Null | Value::Undefined => None,
|
||||||
.get_character_by_export_name(&class_name)
|
Value::Object(Object::FunctionObject(func)) => Some(*func),
|
||||||
{
|
_ => return Ok(Value::Bool(false)),
|
||||||
if let Some(constructor) = args.get(1) {
|
};
|
||||||
movie_clip.set_avm1_constructor(
|
|
||||||
activation.context.gc_context,
|
let class_name = class_name.coerce_to_string(activation)?;
|
||||||
Some(constructor.coerce_to_object(activation)),
|
|
||||||
);
|
let registry = activation
|
||||||
} else {
|
.base_clip()
|
||||||
movie_clip.set_avm1_constructor(activation.context.gc_context, None);
|
.movie()
|
||||||
}
|
.map(|movie| activation.context.library.library_for_movie_mut(movie))
|
||||||
} else {
|
.and_then(|library| library.get_avm1_constructor_registry());
|
||||||
log::warn!(
|
|
||||||
"Tried to register_class on an unknown export {}",
|
match registry {
|
||||||
class_name
|
Some(registry) => {
|
||||||
);
|
registry.set(&class_name, constructor, activation.context.gc_context);
|
||||||
}
|
Ok(Value::Bool(true))
|
||||||
} else {
|
}
|
||||||
log::warn!("Tried to register_class on an unknown movie");
|
None => {
|
||||||
|
log::warn!("Can't register_class without a constructor registry");
|
||||||
|
Ok(Value::Bool(false))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log::warn!("Tried to register_class with an unknown class");
|
|
||||||
}
|
}
|
||||||
Ok(Value::Undefined)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements `Object.prototype.watch`
|
/// Implements `Object.prototype.watch`
|
||||||
|
|
|
@ -893,7 +893,7 @@ mod tests {
|
||||||
audio: &mut NullAudioBackend::new(),
|
audio: &mut NullAudioBackend::new(),
|
||||||
input: &mut NullInputBackend::new(),
|
input: &mut NullInputBackend::new(),
|
||||||
background_color: &mut None,
|
background_color: &mut None,
|
||||||
library: &mut Library::default(),
|
library: &mut Library::empty(gc_context),
|
||||||
navigator: &mut NullNavigatorBackend::new(),
|
navigator: &mut NullNavigatorBackend::new(),
|
||||||
renderer: &mut NullRenderer::new(),
|
renderer: &mut NullRenderer::new(),
|
||||||
locale: &mut NullLocaleBackend::new(),
|
locale: &mut NullLocaleBackend::new(),
|
||||||
|
|
|
@ -54,7 +54,7 @@ where
|
||||||
input: &mut NullInputBackend::new(),
|
input: &mut NullInputBackend::new(),
|
||||||
action_queue: &mut ActionQueue::new(),
|
action_queue: &mut ActionQueue::new(),
|
||||||
background_color: &mut None,
|
background_color: &mut None,
|
||||||
library: &mut Library::default(),
|
library: &mut Library::empty(gc_context),
|
||||||
navigator: &mut NullNavigatorBackend::new(),
|
navigator: &mut NullNavigatorBackend::new(),
|
||||||
renderer: &mut NullRenderer::new(),
|
renderer: &mut NullRenderer::new(),
|
||||||
locale: &mut NullLocaleBackend::new(),
|
locale: &mut NullLocaleBackend::new(),
|
||||||
|
|
|
@ -27,7 +27,7 @@ use crate::vminterface::{AvmObject, AvmType, Instantiator};
|
||||||
use enumset::{EnumSet, EnumSetType};
|
use enumset::{EnumSet, EnumSetType};
|
||||||
use gc_arena::{Collect, Gc, GcCell, MutationContext};
|
use gc_arena::{Collect, Gc, GcCell, MutationContext};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::cell::Ref;
|
use std::cell::{Ref, RefCell};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -58,7 +58,7 @@ pub struct MovieClipData<'gc> {
|
||||||
frame_scripts: Vec<Avm2FrameScript<'gc>>,
|
frame_scripts: Vec<Avm2FrameScript<'gc>>,
|
||||||
has_button_clip_event: bool,
|
has_button_clip_event: bool,
|
||||||
flags: EnumSet<MovieClipFlags>,
|
flags: EnumSet<MovieClipFlags>,
|
||||||
avm_constructor: Option<AvmObject<'gc>>,
|
avm2_constructor: Option<Avm2Object<'gc>>,
|
||||||
drawing: Drawing,
|
drawing: Drawing,
|
||||||
is_focusable: bool,
|
is_focusable: bool,
|
||||||
has_focus: bool,
|
has_focus: bool,
|
||||||
|
@ -72,7 +72,7 @@ unsafe impl<'gc> Collect for MovieClipData<'gc> {
|
||||||
self.base.trace(cc);
|
self.base.trace(cc);
|
||||||
self.static_data.trace(cc);
|
self.static_data.trace(cc);
|
||||||
self.object.trace(cc);
|
self.object.trace(cc);
|
||||||
self.avm_constructor.trace(cc);
|
self.avm2_constructor.trace(cc);
|
||||||
self.frame_scripts.trace(cc);
|
self.frame_scripts.trace(cc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
frame_scripts: Vec::new(),
|
frame_scripts: Vec::new(),
|
||||||
has_button_clip_event: false,
|
has_button_clip_event: false,
|
||||||
flags: EnumSet::empty(),
|
flags: EnumSet::empty(),
|
||||||
avm_constructor: None,
|
avm2_constructor: None,
|
||||||
drawing: Drawing::new(),
|
drawing: Drawing::new(),
|
||||||
is_focusable: false,
|
is_focusable: false,
|
||||||
has_focus: false,
|
has_focus: false,
|
||||||
|
@ -115,14 +115,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
base: Default::default(),
|
base: Default::default(),
|
||||||
static_data: Gc::allocate(
|
static_data: Gc::allocate(
|
||||||
gc_context,
|
gc_context,
|
||||||
MovieClipStatic {
|
MovieClipStatic::with_data(id, swf, num_frames),
|
||||||
id,
|
|
||||||
swf,
|
|
||||||
total_frames: num_frames,
|
|
||||||
audio_stream_info: None,
|
|
||||||
frame_labels: HashMap::new(),
|
|
||||||
scene_labels: HashMap::new(),
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
tag_stream_pos: 0,
|
tag_stream_pos: 0,
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
|
@ -133,7 +126,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
frame_scripts: Vec::new(),
|
frame_scripts: Vec::new(),
|
||||||
has_button_clip_event: false,
|
has_button_clip_event: false,
|
||||||
flags: MovieClipFlags::Playing.into(),
|
flags: MovieClipFlags::Playing.into(),
|
||||||
avm_constructor: None,
|
avm2_constructor: None,
|
||||||
drawing: Drawing::new(),
|
drawing: Drawing::new(),
|
||||||
is_focusable: false,
|
is_focusable: false,
|
||||||
has_focus: false,
|
has_focus: false,
|
||||||
|
@ -840,42 +833,13 @@ impl<'gc> MovieClip<'gc> {
|
||||||
self.0.read().static_data.total_frames
|
self.0.read().static_data.total_frames
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_avm1_constructor(
|
|
||||||
self,
|
|
||||||
gc_context: MutationContext<'gc, '_>,
|
|
||||||
prototype: Option<Avm1Object<'gc>>,
|
|
||||||
) {
|
|
||||||
let mut write = self.0.write(gc_context);
|
|
||||||
|
|
||||||
if write
|
|
||||||
.avm_constructor
|
|
||||||
.map(|c| c.is_avm2_object())
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
log::error!("Blocked attempt to set AVM1 constructor on AVM2 object");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
write.avm_constructor = prototype.map(|o| o.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_avm2_constructor(
|
pub fn set_avm2_constructor(
|
||||||
self,
|
self,
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
prototype: Option<Avm2Object<'gc>>,
|
prototype: Option<Avm2Object<'gc>>,
|
||||||
) {
|
) {
|
||||||
let mut write = self.0.write(gc_context);
|
let mut write = self.0.write(gc_context);
|
||||||
|
write.avm2_constructor = prototype;
|
||||||
if write
|
|
||||||
.avm_constructor
|
|
||||||
.map(|c| c.is_avm1_object())
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
log::error!("Blocked attempt to set AVM2 constructor on AVM1 object");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
write.avm_constructor = prototype.map(|o| o.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frame_label_to_number(self, frame_label: &str) -> Option<FrameNumber> {
|
pub fn frame_label_to_number(self, frame_label: &str) -> Option<FrameNumber> {
|
||||||
|
@ -1399,10 +1363,11 @@ impl<'gc> MovieClip<'gc> {
|
||||||
if self.0.read().object.is_none() {
|
if self.0.read().object.is_none() {
|
||||||
let version = context.swf.version();
|
let version = context.swf.version();
|
||||||
let globals = context.avm1.global_object_cell();
|
let globals = context.avm1.global_object_cell();
|
||||||
|
let avm1_constructor = self.0.read().get_registered_avm1_constructor(context);
|
||||||
|
|
||||||
// If we are running within the AVM, this must be an immediate action.
|
// If we are running within the AVM, this must be an immediate action.
|
||||||
// If we are not, then this must be queued to be ran first-thing
|
// If we are not, then this must be queued to be ran first-thing
|
||||||
if instantiated_by.is_avm() && self.0.read().avm_constructor.is_some() {
|
if let Some(constructor) = avm1_constructor.filter(|_| instantiated_by.is_avm()) {
|
||||||
let mut activation = Avm1Activation::from_nothing(
|
let mut activation = Avm1Activation::from_nothing(
|
||||||
context.reborrow(),
|
context.reborrow(),
|
||||||
ActivationIdentifier::root("[Construct]"),
|
ActivationIdentifier::root("[Construct]"),
|
||||||
|
@ -1411,13 +1376,6 @@ impl<'gc> MovieClip<'gc> {
|
||||||
self.into(),
|
self.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let constructor = self
|
|
||||||
.0
|
|
||||||
.read()
|
|
||||||
.avm_constructor
|
|
||||||
.unwrap()
|
|
||||||
.as_avm1_object()
|
|
||||||
.unwrap();
|
|
||||||
if let Ok(prototype) = constructor
|
if let Ok(prototype) = constructor
|
||||||
.get("prototype", &mut activation)
|
.get("prototype", &mut activation)
|
||||||
.map(|v| v.coerce_to_object(&mut activation))
|
.map(|v| v.coerce_to_object(&mut activation))
|
||||||
|
@ -1488,7 +1446,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
context.action_queue.queue_actions(
|
context.action_queue.queue_actions(
|
||||||
display_object,
|
display_object,
|
||||||
ActionType::Construct {
|
ActionType::Construct {
|
||||||
constructor: mc.avm_constructor.map(|a| a.as_avm1_object().unwrap()),
|
constructor: avm1_constructor,
|
||||||
events,
|
events,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
|
@ -1515,7 +1473,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
display_object: DisplayObject<'gc>,
|
display_object: DisplayObject<'gc>,
|
||||||
) {
|
) {
|
||||||
let constructor = self.0.read().avm_constructor.unwrap_or_else(|| {
|
let mut constructor = self.0.read().avm2_constructor.unwrap_or_else(|| {
|
||||||
let mut activation = Avm2Activation::from_nothing(context.reborrow());
|
let mut activation = Avm2Activation::from_nothing(context.reborrow());
|
||||||
let mut mc_proto = activation.context.avm2.prototypes().movieclip;
|
let mut mc_proto = activation.context.avm2.prototypes().movieclip;
|
||||||
mc_proto
|
mc_proto
|
||||||
|
@ -1527,39 +1485,34 @@ impl<'gc> MovieClip<'gc> {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.coerce_to_object(&mut activation)
|
.coerce_to_object(&mut activation)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let AvmObject::Avm2(mut constr) = constructor {
|
let mut constr_thing = || {
|
||||||
let mut constr_thing = || {
|
let mut activation = Avm2Activation::from_nothing(context.reborrow());
|
||||||
let mut activation = Avm2Activation::from_nothing(context.reborrow());
|
let proto = constructor
|
||||||
let proto = constr
|
.get_property(
|
||||||
.get_property(
|
constructor,
|
||||||
constr,
|
&Avm2QName::new(Avm2Namespace::public_namespace(), "prototype"),
|
||||||
&Avm2QName::new(Avm2Namespace::public_namespace(), "prototype"),
|
&mut activation,
|
||||||
&mut activation,
|
)?
|
||||||
)?
|
.coerce_to_object(&mut activation)?;
|
||||||
.coerce_to_object(&mut activation)?;
|
let object = Avm2StageObject::for_display_object(
|
||||||
let object = Avm2StageObject::for_display_object(
|
activation.context.gc_context,
|
||||||
activation.context.gc_context,
|
display_object,
|
||||||
display_object,
|
proto,
|
||||||
proto,
|
)
|
||||||
)
|
.into();
|
||||||
.into();
|
|
||||||
|
|
||||||
constr.call(Some(object), &[], &mut activation, Some(proto))?;
|
constructor.call(Some(object), &[], &mut activation, Some(proto))?;
|
||||||
|
|
||||||
Ok(object)
|
Ok(object)
|
||||||
};
|
};
|
||||||
let result: Result<Avm2Object<'gc>, Avm2Error> = constr_thing();
|
let result: Result<Avm2Object<'gc>, Avm2Error> = constr_thing();
|
||||||
|
|
||||||
if let Ok(object) = result {
|
if let Ok(object) = result {
|
||||||
self.0.write(context.gc_context).object = Some(object.into());
|
self.0.write(context.gc_context).object = Some(object.into());
|
||||||
} else if let Err(e) = result {
|
} else if let Err(e) = result {
|
||||||
log::error!("Got {} when constructing AVM2 side of display object", e);
|
log::error!("Got {} when constructing AVM2 side of display object", e);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::error!("Attempted to construct AVM2 movieclip with AVM1 constructor!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1928,14 +1881,7 @@ impl<'gc> MovieClipData<'gc> {
|
||||||
self.base.reset_for_movie_load();
|
self.base.reset_for_movie_load();
|
||||||
self.static_data = Gc::allocate(
|
self.static_data = Gc::allocate(
|
||||||
gc_context,
|
gc_context,
|
||||||
MovieClipStatic {
|
MovieClipStatic::with_data(0, movie.into(), total_frames),
|
||||||
id: 0,
|
|
||||||
swf: movie.into(),
|
|
||||||
total_frames,
|
|
||||||
audio_stream_info: None,
|
|
||||||
frame_labels: HashMap::new(),
|
|
||||||
scene_labels: HashMap::new(),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
self.tag_stream_pos = 0;
|
self.tag_stream_pos = 0;
|
||||||
self.flags = MovieClipFlags::Playing.into();
|
self.flags = MovieClipFlags::Playing.into();
|
||||||
|
@ -2130,6 +2076,21 @@ impl<'gc> MovieClipData<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch the avm1 constructor associated with this MovieClip by `Object.registerClass`.
|
||||||
|
/// Return `None` if this MovieClip isn't exported, or if no constructor is associated
|
||||||
|
/// to its symbol name.
|
||||||
|
fn get_registered_avm1_constructor(
|
||||||
|
&self,
|
||||||
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
) -> Option<Avm1Object<'gc>> {
|
||||||
|
let symbol_name = self.static_data.exported_name.borrow();
|
||||||
|
let symbol_name = symbol_name.as_ref()?;
|
||||||
|
let library = context.library.library_for_movie_mut(self.movie());
|
||||||
|
let registry = library.get_avm1_constructor_registry()?;
|
||||||
|
let ctor = registry.get(symbol_name)?;
|
||||||
|
Some(Avm1Object::FunctionObject(ctor))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn movie(&self) -> Arc<SwfMovie> {
|
pub fn movie(&self) -> Arc<SwfMovie> {
|
||||||
self.static_data.swf.movie.clone()
|
self.static_data.swf.movie.clone()
|
||||||
}
|
}
|
||||||
|
@ -2711,10 +2672,15 @@ impl<'gc, 'a> MovieClipData<'gc> {
|
||||||
) -> DecodeResult {
|
) -> DecodeResult {
|
||||||
let exports = reader.read_export_assets()?;
|
let exports = reader.read_export_assets()?;
|
||||||
for export in exports {
|
for export in exports {
|
||||||
context
|
let character = context
|
||||||
.library
|
.library
|
||||||
.library_for_movie_mut(self.movie())
|
.library_for_movie_mut(self.movie())
|
||||||
.register_export(export.id, &export.name);
|
.register_export(export.id, &export.name);
|
||||||
|
|
||||||
|
// TODO: do other types of Character need to know their exported name?
|
||||||
|
if let Some(Character::MovieClip(movie_clip)) = character {
|
||||||
|
*movie_clip.0.read().static_data.exported_name.borrow_mut() = Some(export.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2997,7 +2963,8 @@ impl Default for Scene {
|
||||||
|
|
||||||
/// Static data shared between all instances of a movie clip.
|
/// Static data shared between all instances of a movie clip.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Collect)]
|
||||||
|
#[collect(require_static)]
|
||||||
struct MovieClipStatic {
|
struct MovieClipStatic {
|
||||||
id: CharacterId,
|
id: CharacterId,
|
||||||
swf: SwfSlice,
|
swf: SwfSlice,
|
||||||
|
@ -3005,28 +2972,29 @@ struct MovieClipStatic {
|
||||||
scene_labels: HashMap<String, Scene>,
|
scene_labels: HashMap<String, Scene>,
|
||||||
audio_stream_info: Option<swf::SoundStreamHead>,
|
audio_stream_info: Option<swf::SoundStreamHead>,
|
||||||
total_frames: FrameNumber,
|
total_frames: FrameNumber,
|
||||||
|
/// The last known symbol name under which this movie clip was exported.
|
||||||
|
/// Used for looking up constructors registered with `Object.registerClass`.
|
||||||
|
exported_name: RefCell<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MovieClipStatic {
|
impl MovieClipStatic {
|
||||||
fn empty(swf: SwfSlice) -> Self {
|
fn empty(swf: SwfSlice) -> Self {
|
||||||
|
Self::with_data(0, swf, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_data(id: CharacterId, swf: SwfSlice, total_frames: FrameNumber) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: 0,
|
id,
|
||||||
swf,
|
swf,
|
||||||
total_frames: 1,
|
total_frames,
|
||||||
frame_labels: HashMap::new(),
|
frame_labels: HashMap::new(),
|
||||||
scene_labels: HashMap::new(),
|
scene_labels: HashMap::new(),
|
||||||
audio_stream_info: None,
|
audio_stream_info: None,
|
||||||
|
exported_name: RefCell::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<'gc> Collect for MovieClipStatic {
|
|
||||||
#[inline]
|
|
||||||
fn needs_trace() -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores the placement settings for display objects during a
|
/// Stores the placement settings for display objects during a
|
||||||
/// goto command.
|
/// goto command.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::avm2::Domain as Avm2Domain;
|
|
||||||
use crate::backend::audio::SoundHandle;
|
use crate::backend::audio::SoundHandle;
|
||||||
use crate::character::Character;
|
use crate::character::Character;
|
||||||
use crate::display_object::{Bitmap, TDisplayObject};
|
use crate::display_object::{Bitmap, TDisplayObject};
|
||||||
|
@ -7,7 +6,8 @@ use crate::prelude::*;
|
||||||
use crate::property_map::{Entry, PropertyMap};
|
use crate::property_map::{Entry, PropertyMap};
|
||||||
use crate::tag_utils::{SwfMovie, SwfSlice};
|
use crate::tag_utils::{SwfMovie, SwfSlice};
|
||||||
use crate::vminterface::AvmType;
|
use crate::vminterface::AvmType;
|
||||||
use gc_arena::{Collect, MutationContext};
|
use crate::{avm1::function::FunctionObject, avm2::Domain as Avm2Domain};
|
||||||
|
use gc_arena::{Collect, Gc, GcCell, MutationContext};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use swf::{CharacterId, TagCode};
|
use swf::{CharacterId, TagCode};
|
||||||
|
@ -16,6 +16,45 @@ use weak_table::PtrWeakKeyHashMap;
|
||||||
/// Boxed error alias.
|
/// Boxed error alias.
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
|
/// The mappings between symbol names and constructors registered
|
||||||
|
/// with `Object.registerClass`.
|
||||||
|
#[derive(Collect)]
|
||||||
|
#[collect(no_drop)]
|
||||||
|
pub struct Avm1ConstructorRegistry<'gc> {
|
||||||
|
symbol_map: GcCell<'gc, PropertyMap<FunctionObject<'gc>>>,
|
||||||
|
is_case_sensitive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> Avm1ConstructorRegistry<'gc> {
|
||||||
|
pub fn new(is_case_sensitive: bool, gc_context: MutationContext<'gc, '_>) -> Self {
|
||||||
|
Self {
|
||||||
|
symbol_map: GcCell::allocate(gc_context, PropertyMap::new()),
|
||||||
|
is_case_sensitive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, symbol: &str) -> Option<FunctionObject<'gc>> {
|
||||||
|
self.symbol_map
|
||||||
|
.read()
|
||||||
|
.get(symbol, self.is_case_sensitive)
|
||||||
|
.copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(
|
||||||
|
&self,
|
||||||
|
symbol: &str,
|
||||||
|
constructor: Option<FunctionObject<'gc>>,
|
||||||
|
gc_context: MutationContext<'gc, '_>,
|
||||||
|
) {
|
||||||
|
let mut map = self.symbol_map.write(gc_context);
|
||||||
|
if let Some(ctor) = constructor {
|
||||||
|
map.insert(symbol, ctor, self.is_case_sensitive);
|
||||||
|
} else {
|
||||||
|
map.remove(symbol, self.is_case_sensitive);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Symbol library for a single given SWF.
|
/// Symbol library for a single given SWF.
|
||||||
#[derive(Collect)]
|
#[derive(Collect)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
|
@ -26,6 +65,9 @@ pub struct MovieLibrary<'gc> {
|
||||||
fonts: HashMap<FontDescriptor, Font<'gc>>,
|
fonts: HashMap<FontDescriptor, Font<'gc>>,
|
||||||
avm_type: AvmType,
|
avm_type: AvmType,
|
||||||
avm2_domain: Option<Avm2Domain<'gc>>,
|
avm2_domain: Option<Avm2Domain<'gc>>,
|
||||||
|
/// Shared reference to the constructor registry used for this movie.
|
||||||
|
/// Should be `None` if this is an AVM2 movie.
|
||||||
|
avm1_constructor_registry: Option<Gc<'gc, Avm1ConstructorRegistry<'gc>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> MovieLibrary<'gc> {
|
impl<'gc> MovieLibrary<'gc> {
|
||||||
|
@ -37,6 +79,7 @@ impl<'gc> MovieLibrary<'gc> {
|
||||||
fonts: HashMap::new(),
|
fonts: HashMap::new(),
|
||||||
avm_type,
|
avm_type,
|
||||||
avm2_domain: None,
|
avm2_domain: None,
|
||||||
|
avm1_constructor_registry: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,11 +98,16 @@ impl<'gc> MovieLibrary<'gc> {
|
||||||
|
|
||||||
/// Registers an export name for a given character ID.
|
/// Registers an export name for a given character ID.
|
||||||
/// This character will then be instantiable from AVM1.
|
/// This character will then be instantiable from AVM1.
|
||||||
pub fn register_export(&mut self, id: CharacterId, export_name: &str) {
|
pub fn register_export(
|
||||||
|
&mut self,
|
||||||
|
id: CharacterId,
|
||||||
|
export_name: &str,
|
||||||
|
) -> Option<&Character<'gc>> {
|
||||||
if let Some(character) = self.characters.get(&id) {
|
if let Some(character) = self.characters.get(&id) {
|
||||||
match self.export_characters.entry(export_name, false) {
|
match self.export_characters.entry(export_name, false) {
|
||||||
Entry::Vacant(e) => {
|
Entry::Vacant(e) => {
|
||||||
e.insert(character.clone());
|
e.insert(character.clone());
|
||||||
|
return Some(character);
|
||||||
}
|
}
|
||||||
Entry::Occupied(_) => {
|
Entry::Occupied(_) => {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
|
@ -75,6 +123,7 @@ impl<'gc> MovieLibrary<'gc> {
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_character(&self, id: CharacterId) -> bool {
|
pub fn contains_character(&self, id: CharacterId) -> bool {
|
||||||
|
@ -91,6 +140,10 @@ impl<'gc> MovieLibrary<'gc> {
|
||||||
self.export_characters.get(name, false)
|
self.export_characters.get(name, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_avm1_constructor_registry(&self) -> Option<Gc<'gc, Avm1ConstructorRegistry<'gc>>> {
|
||||||
|
self.avm1_constructor_registry
|
||||||
|
}
|
||||||
|
|
||||||
/// Instantiates the library item with the given character ID into a display object.
|
/// Instantiates the library item with the given character ID into a display object.
|
||||||
/// The object must then be post-instantiated before being used.
|
/// The object must then be post-instantiated before being used.
|
||||||
pub fn instantiate_by_id(
|
pub fn instantiate_by_id(
|
||||||
|
@ -242,6 +295,9 @@ pub struct Library<'gc> {
|
||||||
|
|
||||||
/// The embedded device font.
|
/// The embedded device font.
|
||||||
device_font: Option<Font<'gc>>,
|
device_font: Option<Font<'gc>>,
|
||||||
|
|
||||||
|
constructor_registry_case_insensitive: Gc<'gc, Avm1ConstructorRegistry<'gc>>,
|
||||||
|
constructor_registry_case_sensitive: Gc<'gc, Avm1ConstructorRegistry<'gc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<'gc> gc_arena::Collect for Library<'gc> {
|
unsafe impl<'gc> gc_arena::Collect for Library<'gc> {
|
||||||
|
@ -251,10 +307,27 @@ unsafe impl<'gc> gc_arena::Collect for Library<'gc> {
|
||||||
val.trace(cc);
|
val.trace(cc);
|
||||||
}
|
}
|
||||||
self.device_font.trace(cc);
|
self.device_font.trace(cc);
|
||||||
|
self.constructor_registry_case_insensitive.trace(cc);
|
||||||
|
self.constructor_registry_case_sensitive.trace(cc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> Library<'gc> {
|
impl<'gc> Library<'gc> {
|
||||||
|
pub fn empty(gc_context: MutationContext<'gc, '_>) -> Self {
|
||||||
|
Self {
|
||||||
|
movie_libraries: PtrWeakKeyHashMap::new(),
|
||||||
|
device_font: None,
|
||||||
|
constructor_registry_case_insensitive: Gc::allocate(
|
||||||
|
gc_context,
|
||||||
|
Avm1ConstructorRegistry::new(false, gc_context),
|
||||||
|
),
|
||||||
|
constructor_registry_case_sensitive: Gc::allocate(
|
||||||
|
gc_context,
|
||||||
|
Avm1ConstructorRegistry::new(true, gc_context),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn library_for_movie(&self, movie: Arc<SwfMovie>) -> Option<&MovieLibrary<'gc>> {
|
pub fn library_for_movie(&self, movie: Arc<SwfMovie>) -> Option<&MovieLibrary<'gc>> {
|
||||||
self.movie_libraries.get(&movie)
|
self.movie_libraries.get(&movie)
|
||||||
}
|
}
|
||||||
|
@ -263,7 +336,8 @@ impl<'gc> Library<'gc> {
|
||||||
if !self.movie_libraries.contains_key(&movie) {
|
if !self.movie_libraries.contains_key(&movie) {
|
||||||
let slice = SwfSlice::from(movie.clone());
|
let slice = SwfSlice::from(movie.clone());
|
||||||
let mut reader = slice.read_from(0);
|
let mut reader = slice.read_from(0);
|
||||||
let vm_type = if movie.header().version > 8 {
|
let movie_version = movie.header().version;
|
||||||
|
let vm_type = if movie_version > 8 {
|
||||||
match reader.read_tag_code_and_length() {
|
match reader.read_tag_code_and_length() {
|
||||||
Ok((tag_code, _tag_len))
|
Ok((tag_code, _tag_len))
|
||||||
if TagCode::from_u16(tag_code) == Some(TagCode::FileAttributes) =>
|
if TagCode::from_u16(tag_code) == Some(TagCode::FileAttributes) =>
|
||||||
|
@ -284,8 +358,13 @@ impl<'gc> Library<'gc> {
|
||||||
AvmType::Avm1
|
AvmType::Avm1
|
||||||
};
|
};
|
||||||
|
|
||||||
self.movie_libraries
|
let mut movie_library = MovieLibrary::new(vm_type);
|
||||||
.insert(movie.clone(), MovieLibrary::new(vm_type));
|
if vm_type == AvmType::Avm1 {
|
||||||
|
movie_library.avm1_constructor_registry =
|
||||||
|
Some(self.get_avm1_constructor_registry(movie_version));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.movie_libraries.insert(movie.clone(), movie_library);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.movie_libraries.get_mut(&movie).unwrap()
|
self.movie_libraries.get_mut(&movie).unwrap()
|
||||||
|
@ -300,13 +379,18 @@ impl<'gc> Library<'gc> {
|
||||||
pub fn set_device_font(&mut self, font: Option<Font<'gc>>) {
|
pub fn set_device_font(&mut self, font: Option<Font<'gc>>) {
|
||||||
self.device_font = font;
|
self.device_font = font;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'gc> Default for Library<'gc> {
|
/// Gets the constructor registry to use for the given SWF version.
|
||||||
fn default() -> Self {
|
/// Because SWFs v6 and v7+ use different case-sensitivity rules, Flash
|
||||||
Self {
|
/// keeps two separate registries, one case-sensitive, the other not.
|
||||||
movie_libraries: PtrWeakKeyHashMap::new(),
|
fn get_avm1_constructor_registry(
|
||||||
device_font: None,
|
&mut self,
|
||||||
|
swf_version: u8,
|
||||||
|
) -> Gc<'gc, Avm1ConstructorRegistry<'gc>> {
|
||||||
|
if swf_version < 7 {
|
||||||
|
self.constructor_registry_case_insensitive
|
||||||
|
} else {
|
||||||
|
self.constructor_registry_case_sensitive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,7 +260,7 @@ impl Player {
|
||||||
GcRoot(GcCell::allocate(
|
GcRoot(GcCell::allocate(
|
||||||
gc_context,
|
gc_context,
|
||||||
GcRootData {
|
GcRootData {
|
||||||
library: Library::default(),
|
library: Library::empty(gc_context),
|
||||||
levels: BTreeMap::new(),
|
levels: BTreeMap::new(),
|
||||||
mouse_hovered_object: None,
|
mouse_hovered_object: None,
|
||||||
drag_object: None,
|
drag_object: None,
|
||||||
|
|
Loading…
Reference in New Issue