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:
Moulins 2021-01-10 16:45:54 +01:00 committed by Mike Welsh
parent 9bb36885bb
commit f394953331
6 changed files with 194 additions and 144 deletions

View File

@ -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 {
[class_name, constructor, ..] => (class_name, constructor),
_ => return Ok(Value::Bool(false)),
};
let constructor = match constructor {
Value::Null | Value::Undefined => None,
Value::Object(Object::FunctionObject(func)) => Some(*func),
_ => return Ok(Value::Bool(false)),
};
let class_name = class_name.coerce_to_string(activation)?; let class_name = class_name.coerce_to_string(activation)?;
if let Some(movie) = activation.base_clip().movie() {
if let Some(Character::MovieClip(movie_clip)) = activation let registry = activation
.context .base_clip()
.library .movie()
.library_for_movie_mut(movie) .map(|movie| activation.context.library.library_for_movie_mut(movie))
.get_character_by_export_name(&class_name) .and_then(|library| library.get_avm1_constructor_registry());
{
if let Some(constructor) = args.get(1) { match registry {
movie_clip.set_avm1_constructor( Some(registry) => {
activation.context.gc_context, registry.set(&class_name, constructor, activation.context.gc_context);
Some(constructor.coerce_to_object(activation)), Ok(Value::Bool(true))
);
} else {
movie_clip.set_avm1_constructor(activation.context.gc_context, None);
} }
} else { None => {
log::warn!( log::warn!("Can't register_class without a constructor registry");
"Tried to register_class on an unknown export {}", Ok(Value::Bool(false))
class_name
);
} }
} else {
log::warn!("Tried to register_class on an unknown movie");
} }
} else {
log::warn!("Tried to register_class with an unknown class");
}
Ok(Value::Undefined)
} }
/// Implements `Object.prototype.watch` /// Implements `Object.prototype.watch`

View File

@ -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(),

View File

@ -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(),

View File

@ -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,15 +1485,13 @@ 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 = constr let proto = constructor
.get_property( .get_property(
constr, constructor,
&Avm2QName::new(Avm2Namespace::public_namespace(), "prototype"), &Avm2QName::new(Avm2Namespace::public_namespace(), "prototype"),
&mut activation, &mut activation,
)? )?
@ -1547,7 +1503,7 @@ impl<'gc> MovieClip<'gc> {
) )
.into(); .into();
constr.call(Some(object), &[], &mut activation, Some(proto))?; constructor.call(Some(object), &[], &mut activation, Some(proto))?;
Ok(object) Ok(object)
}; };
@ -1558,9 +1514,6 @@ impl<'gc> MovieClip<'gc> {
} 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!");
}
} }
pub fn register_frame_script( pub fn register_frame_script(
@ -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)]

View File

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

View File

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