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::{Object, ScriptObject, TObject, Value};
|
||||
use crate::avm_warn;
|
||||
use crate::character::Character;
|
||||
use crate::display_object::TDisplayObject;
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::MutationContext;
|
||||
|
@ -147,36 +146,35 @@ pub fn register_class<'gc>(
|
|||
_this: Object<'gc>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(class_name) = args.get(0).cloned() {
|
||||
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
|
||||
.context
|
||||
.library
|
||||
.library_for_movie_mut(movie)
|
||||
.get_character_by_export_name(&class_name)
|
||||
{
|
||||
if let Some(constructor) = args.get(1) {
|
||||
movie_clip.set_avm1_constructor(
|
||||
activation.context.gc_context,
|
||||
Some(constructor.coerce_to_object(activation)),
|
||||
);
|
||||
} else {
|
||||
movie_clip.set_avm1_constructor(activation.context.gc_context, None);
|
||||
}
|
||||
} else {
|
||||
log::warn!(
|
||||
"Tried to register_class on an unknown export {}",
|
||||
class_name
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::warn!("Tried to register_class on an unknown movie");
|
||||
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 registry = activation
|
||||
.base_clip()
|
||||
.movie()
|
||||
.map(|movie| activation.context.library.library_for_movie_mut(movie))
|
||||
.and_then(|library| library.get_avm1_constructor_registry());
|
||||
|
||||
match registry {
|
||||
Some(registry) => {
|
||||
registry.set(&class_name, constructor, activation.context.gc_context);
|
||||
Ok(Value::Bool(true))
|
||||
}
|
||||
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`
|
||||
|
|
|
@ -893,7 +893,7 @@ mod tests {
|
|||
audio: &mut NullAudioBackend::new(),
|
||||
input: &mut NullInputBackend::new(),
|
||||
background_color: &mut None,
|
||||
library: &mut Library::default(),
|
||||
library: &mut Library::empty(gc_context),
|
||||
navigator: &mut NullNavigatorBackend::new(),
|
||||
renderer: &mut NullRenderer::new(),
|
||||
locale: &mut NullLocaleBackend::new(),
|
||||
|
|
|
@ -54,7 +54,7 @@ where
|
|||
input: &mut NullInputBackend::new(),
|
||||
action_queue: &mut ActionQueue::new(),
|
||||
background_color: &mut None,
|
||||
library: &mut Library::default(),
|
||||
library: &mut Library::empty(gc_context),
|
||||
navigator: &mut NullNavigatorBackend::new(),
|
||||
renderer: &mut NullRenderer::new(),
|
||||
locale: &mut NullLocaleBackend::new(),
|
||||
|
|
|
@ -27,7 +27,7 @@ use crate::vminterface::{AvmObject, AvmType, Instantiator};
|
|||
use enumset::{EnumSet, EnumSetType};
|
||||
use gc_arena::{Collect, Gc, GcCell, MutationContext};
|
||||
use smallvec::SmallVec;
|
||||
use std::cell::Ref;
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
|
@ -58,7 +58,7 @@ pub struct MovieClipData<'gc> {
|
|||
frame_scripts: Vec<Avm2FrameScript<'gc>>,
|
||||
has_button_clip_event: bool,
|
||||
flags: EnumSet<MovieClipFlags>,
|
||||
avm_constructor: Option<AvmObject<'gc>>,
|
||||
avm2_constructor: Option<Avm2Object<'gc>>,
|
||||
drawing: Drawing,
|
||||
is_focusable: bool,
|
||||
has_focus: bool,
|
||||
|
@ -72,7 +72,7 @@ unsafe impl<'gc> Collect for MovieClipData<'gc> {
|
|||
self.base.trace(cc);
|
||||
self.static_data.trace(cc);
|
||||
self.object.trace(cc);
|
||||
self.avm_constructor.trace(cc);
|
||||
self.avm2_constructor.trace(cc);
|
||||
self.frame_scripts.trace(cc);
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
frame_scripts: Vec::new(),
|
||||
has_button_clip_event: false,
|
||||
flags: EnumSet::empty(),
|
||||
avm_constructor: None,
|
||||
avm2_constructor: None,
|
||||
drawing: Drawing::new(),
|
||||
is_focusable: false,
|
||||
has_focus: false,
|
||||
|
@ -115,14 +115,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
base: Default::default(),
|
||||
static_data: Gc::allocate(
|
||||
gc_context,
|
||||
MovieClipStatic {
|
||||
id,
|
||||
swf,
|
||||
total_frames: num_frames,
|
||||
audio_stream_info: None,
|
||||
frame_labels: HashMap::new(),
|
||||
scene_labels: HashMap::new(),
|
||||
},
|
||||
MovieClipStatic::with_data(id, swf, num_frames),
|
||||
),
|
||||
tag_stream_pos: 0,
|
||||
current_frame: 0,
|
||||
|
@ -133,7 +126,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
frame_scripts: Vec::new(),
|
||||
has_button_clip_event: false,
|
||||
flags: MovieClipFlags::Playing.into(),
|
||||
avm_constructor: None,
|
||||
avm2_constructor: None,
|
||||
drawing: Drawing::new(),
|
||||
is_focusable: false,
|
||||
has_focus: false,
|
||||
|
@ -840,42 +833,13 @@ impl<'gc> MovieClip<'gc> {
|
|||
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(
|
||||
self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
prototype: Option<Avm2Object<'gc>>,
|
||||
) {
|
||||
let mut write = self.0.write(gc_context);
|
||||
|
||||
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());
|
||||
write.avm2_constructor = prototype;
|
||||
}
|
||||
|
||||
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() {
|
||||
let version = context.swf.version();
|
||||
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 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(
|
||||
context.reborrow(),
|
||||
ActivationIdentifier::root("[Construct]"),
|
||||
|
@ -1411,13 +1376,6 @@ impl<'gc> MovieClip<'gc> {
|
|||
self.into(),
|
||||
);
|
||||
|
||||
let constructor = self
|
||||
.0
|
||||
.read()
|
||||
.avm_constructor
|
||||
.unwrap()
|
||||
.as_avm1_object()
|
||||
.unwrap();
|
||||
if let Ok(prototype) = constructor
|
||||
.get("prototype", &mut activation)
|
||||
.map(|v| v.coerce_to_object(&mut activation))
|
||||
|
@ -1488,7 +1446,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
context.action_queue.queue_actions(
|
||||
display_object,
|
||||
ActionType::Construct {
|
||||
constructor: mc.avm_constructor.map(|a| a.as_avm1_object().unwrap()),
|
||||
constructor: avm1_constructor,
|
||||
events,
|
||||
},
|
||||
false,
|
||||
|
@ -1515,7 +1473,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
context: &mut UpdateContext<'_, '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 mc_proto = activation.context.avm2.prototypes().movieclip;
|
||||
mc_proto
|
||||
|
@ -1527,39 +1485,34 @@ impl<'gc> MovieClip<'gc> {
|
|||
.unwrap()
|
||||
.coerce_to_object(&mut activation)
|
||||
.unwrap()
|
||||
.into()
|
||||
});
|
||||
|
||||
if let AvmObject::Avm2(mut constr) = constructor {
|
||||
let mut constr_thing = || {
|
||||
let mut activation = Avm2Activation::from_nothing(context.reborrow());
|
||||
let proto = constr
|
||||
.get_property(
|
||||
constr,
|
||||
&Avm2QName::new(Avm2Namespace::public_namespace(), "prototype"),
|
||||
&mut activation,
|
||||
)?
|
||||
.coerce_to_object(&mut activation)?;
|
||||
let object = Avm2StageObject::for_display_object(
|
||||
activation.context.gc_context,
|
||||
display_object,
|
||||
proto,
|
||||
)
|
||||
.into();
|
||||
let mut constr_thing = || {
|
||||
let mut activation = Avm2Activation::from_nothing(context.reborrow());
|
||||
let proto = constructor
|
||||
.get_property(
|
||||
constructor,
|
||||
&Avm2QName::new(Avm2Namespace::public_namespace(), "prototype"),
|
||||
&mut activation,
|
||||
)?
|
||||
.coerce_to_object(&mut activation)?;
|
||||
let object = Avm2StageObject::for_display_object(
|
||||
activation.context.gc_context,
|
||||
display_object,
|
||||
proto,
|
||||
)
|
||||
.into();
|
||||
|
||||
constr.call(Some(object), &[], &mut activation, Some(proto))?;
|
||||
constructor.call(Some(object), &[], &mut activation, Some(proto))?;
|
||||
|
||||
Ok(object)
|
||||
};
|
||||
let result: Result<Avm2Object<'gc>, Avm2Error> = constr_thing();
|
||||
Ok(object)
|
||||
};
|
||||
let result: Result<Avm2Object<'gc>, Avm2Error> = constr_thing();
|
||||
|
||||
if let Ok(object) = result {
|
||||
self.0.write(context.gc_context).object = Some(object.into());
|
||||
} else if let Err(e) = result {
|
||||
log::error!("Got {} when constructing AVM2 side of display object", e);
|
||||
}
|
||||
} else {
|
||||
log::error!("Attempted to construct AVM2 movieclip with AVM1 constructor!");
|
||||
if let Ok(object) = result {
|
||||
self.0.write(context.gc_context).object = Some(object.into());
|
||||
} else if let Err(e) = result {
|
||||
log::error!("Got {} when constructing AVM2 side of display object", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1928,14 +1881,7 @@ impl<'gc> MovieClipData<'gc> {
|
|||
self.base.reset_for_movie_load();
|
||||
self.static_data = Gc::allocate(
|
||||
gc_context,
|
||||
MovieClipStatic {
|
||||
id: 0,
|
||||
swf: movie.into(),
|
||||
total_frames,
|
||||
audio_stream_info: None,
|
||||
frame_labels: HashMap::new(),
|
||||
scene_labels: HashMap::new(),
|
||||
},
|
||||
MovieClipStatic::with_data(0, movie.into(), total_frames),
|
||||
);
|
||||
self.tag_stream_pos = 0;
|
||||
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> {
|
||||
self.static_data.swf.movie.clone()
|
||||
}
|
||||
|
@ -2711,10 +2672,15 @@ impl<'gc, 'a> MovieClipData<'gc> {
|
|||
) -> DecodeResult {
|
||||
let exports = reader.read_export_assets()?;
|
||||
for export in exports {
|
||||
context
|
||||
let character = context
|
||||
.library
|
||||
.library_for_movie_mut(self.movie())
|
||||
.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(())
|
||||
}
|
||||
|
@ -2997,7 +2963,8 @@ impl Default for Scene {
|
|||
|
||||
/// Static data shared between all instances of a movie clip.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Collect)]
|
||||
#[collect(require_static)]
|
||||
struct MovieClipStatic {
|
||||
id: CharacterId,
|
||||
swf: SwfSlice,
|
||||
|
@ -3005,28 +2972,29 @@ struct MovieClipStatic {
|
|||
scene_labels: HashMap<String, Scene>,
|
||||
audio_stream_info: Option<swf::SoundStreamHead>,
|
||||
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 {
|
||||
fn empty(swf: SwfSlice) -> Self {
|
||||
Self::with_data(0, swf, 1)
|
||||
}
|
||||
|
||||
fn with_data(id: CharacterId, swf: SwfSlice, total_frames: FrameNumber) -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
id,
|
||||
swf,
|
||||
total_frames: 1,
|
||||
total_frames,
|
||||
frame_labels: HashMap::new(),
|
||||
scene_labels: HashMap::new(),
|
||||
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
|
||||
/// goto command.
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::avm2::Domain as Avm2Domain;
|
||||
use crate::backend::audio::SoundHandle;
|
||||
use crate::character::Character;
|
||||
use crate::display_object::{Bitmap, TDisplayObject};
|
||||
|
@ -7,7 +6,8 @@ use crate::prelude::*;
|
|||
use crate::property_map::{Entry, PropertyMap};
|
||||
use crate::tag_utils::{SwfMovie, SwfSlice};
|
||||
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::sync::{Arc, Weak};
|
||||
use swf::{CharacterId, TagCode};
|
||||
|
@ -16,6 +16,45 @@ use weak_table::PtrWeakKeyHashMap;
|
|||
/// Boxed error alias.
|
||||
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.
|
||||
#[derive(Collect)]
|
||||
#[collect(no_drop)]
|
||||
|
@ -26,6 +65,9 @@ pub struct MovieLibrary<'gc> {
|
|||
fonts: HashMap<FontDescriptor, Font<'gc>>,
|
||||
avm_type: AvmType,
|
||||
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> {
|
||||
|
@ -37,6 +79,7 @@ impl<'gc> MovieLibrary<'gc> {
|
|||
fonts: HashMap::new(),
|
||||
avm_type,
|
||||
avm2_domain: None,
|
||||
avm1_constructor_registry: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,11 +98,16 @@ impl<'gc> MovieLibrary<'gc> {
|
|||
|
||||
/// Registers an export name for a given character ID.
|
||||
/// 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) {
|
||||
match self.export_characters.entry(export_name, false) {
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(character.clone());
|
||||
return Some(character);
|
||||
}
|
||||
Entry::Occupied(_) => {
|
||||
log::warn!(
|
||||
|
@ -75,6 +123,7 @@ impl<'gc> MovieLibrary<'gc> {
|
|||
id
|
||||
)
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn contains_character(&self, id: CharacterId) -> bool {
|
||||
|
@ -91,6 +140,10 @@ impl<'gc> MovieLibrary<'gc> {
|
|||
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.
|
||||
/// The object must then be post-instantiated before being used.
|
||||
pub fn instantiate_by_id(
|
||||
|
@ -242,6 +295,9 @@ pub struct Library<'gc> {
|
|||
|
||||
/// The embedded device font.
|
||||
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> {
|
||||
|
@ -251,10 +307,27 @@ unsafe impl<'gc> gc_arena::Collect for Library<'gc> {
|
|||
val.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> {
|
||||
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>> {
|
||||
self.movie_libraries.get(&movie)
|
||||
}
|
||||
|
@ -263,7 +336,8 @@ impl<'gc> Library<'gc> {
|
|||
if !self.movie_libraries.contains_key(&movie) {
|
||||
let slice = SwfSlice::from(movie.clone());
|
||||
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() {
|
||||
Ok((tag_code, _tag_len))
|
||||
if TagCode::from_u16(tag_code) == Some(TagCode::FileAttributes) =>
|
||||
|
@ -284,8 +358,13 @@ impl<'gc> Library<'gc> {
|
|||
AvmType::Avm1
|
||||
};
|
||||
|
||||
self.movie_libraries
|
||||
.insert(movie.clone(), MovieLibrary::new(vm_type));
|
||||
let mut movie_library = 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()
|
||||
|
@ -300,13 +379,18 @@ impl<'gc> Library<'gc> {
|
|||
pub fn set_device_font(&mut self, font: Option<Font<'gc>>) {
|
||||
self.device_font = font;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Default for Library<'gc> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
movie_libraries: PtrWeakKeyHashMap::new(),
|
||||
device_font: None,
|
||||
/// Gets the constructor registry to use for the given SWF version.
|
||||
/// Because SWFs v6 and v7+ use different case-sensitivity rules, Flash
|
||||
/// keeps two separate registries, one case-sensitive, the other not.
|
||||
fn get_avm1_constructor_registry(
|
||||
&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(
|
||||
gc_context,
|
||||
GcRootData {
|
||||
library: Library::default(),
|
||||
library: Library::empty(gc_context),
|
||||
levels: BTreeMap::new(),
|
||||
mouse_hovered_object: None,
|
||||
drag_object: None,
|
||||
|
|
Loading…
Reference in New Issue