2021-05-03 19:15:59 +00:00
|
|
|
use crate::avm1::function::FunctionObject;
|
|
|
|
use crate::avm1::property_map::PropertyMap as Avm1PropertyMap;
|
|
|
|
use crate::avm2::{Domain as Avm2Domain, Object as Avm2Object};
|
2019-05-05 22:55:27 +00:00
|
|
|
use crate::backend::audio::SoundHandle;
|
2019-04-25 17:52:22 +00:00
|
|
|
use crate::character::Character;
|
2020-12-30 23:35:43 +00:00
|
|
|
use crate::display_object::{Bitmap, TDisplayObject};
|
2020-05-19 02:12:08 +00:00
|
|
|
use crate::font::{Font, FontDescriptor};
|
2019-05-12 16:55:48 +00:00
|
|
|
use crate::prelude::*;
|
2020-10-06 02:05:11 +00:00
|
|
|
use crate::tag_utils::{SwfMovie, SwfSlice};
|
2020-08-07 02:46:53 +00:00
|
|
|
use crate::vminterface::AvmType;
|
2021-01-10 15:45:54 +00:00
|
|
|
use gc_arena::{Collect, Gc, GcCell, MutationContext};
|
2019-04-25 17:52:22 +00:00
|
|
|
use std::collections::HashMap;
|
2019-11-14 02:41:38 +00:00
|
|
|
use std::sync::{Arc, Weak};
|
2020-10-06 01:41:51 +00:00
|
|
|
use swf::{CharacterId, TagCode};
|
2021-04-10 00:32:04 +00:00
|
|
|
use weak_table::{traits::WeakElement, PtrWeakKeyHashMap, WeakValueHashMap};
|
2019-04-25 17:52:22 +00:00
|
|
|
|
2020-08-07 02:46:53 +00:00
|
|
|
/// Boxed error alias.
|
|
|
|
type Error = Box<dyn std::error::Error>;
|
|
|
|
|
2021-01-10 15:45:54 +00:00
|
|
|
/// The mappings between symbol names and constructors registered
|
|
|
|
/// with `Object.registerClass`.
|
|
|
|
#[derive(Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct Avm1ConstructorRegistry<'gc> {
|
2021-05-03 19:15:59 +00:00
|
|
|
symbol_map: GcCell<'gc, Avm1PropertyMap<FunctionObject<'gc>>>,
|
2021-01-10 15:45:54 +00:00
|
|
|
is_case_sensitive: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Avm1ConstructorRegistry<'gc> {
|
|
|
|
pub fn new(is_case_sensitive: bool, gc_context: MutationContext<'gc, '_>) -> Self {
|
|
|
|
Self {
|
2021-05-03 19:15:59 +00:00
|
|
|
symbol_map: GcCell::allocate(gc_context, Avm1PropertyMap::new()),
|
2021-01-10 15:45:54 +00:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-10 00:32:04 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
struct MovieSymbol(Arc<SwfMovie>, CharacterId);
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct WeakMovieSymbol(Weak<SwfMovie>, CharacterId);
|
|
|
|
|
|
|
|
impl WeakElement for WeakMovieSymbol {
|
|
|
|
type Strong = MovieSymbol;
|
|
|
|
|
|
|
|
fn new(view: &Self::Strong) -> Self {
|
|
|
|
Self(Arc::downgrade(&view.0), view.1)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn view(&self) -> Option<Self::Strong> {
|
|
|
|
if let Some(strong) = self.0.upgrade() {
|
|
|
|
Some(MovieSymbol(strong, self.1))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-23 02:06:38 +00:00
|
|
|
/// The mappings between constructors and library characters defined by
|
2021-04-09 23:15:22 +00:00
|
|
|
/// `SymbolClass`.
|
|
|
|
pub struct Avm2ConstructorRegistry<'gc> {
|
2021-04-23 02:06:38 +00:00
|
|
|
/// A list of AVM2 class constructors and the character IDs they are expected
|
2021-04-09 23:15:22 +00:00
|
|
|
/// to instantiate.
|
2021-04-23 02:06:38 +00:00
|
|
|
constr_map: WeakValueHashMap<Avm2Object<'gc>, WeakMovieSymbol>,
|
2021-04-10 00:32:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl Collect for Avm2ConstructorRegistry<'_> {
|
|
|
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
2021-04-23 02:06:38 +00:00
|
|
|
for (k, _) in self.constr_map.iter() {
|
2021-04-10 00:32:04 +00:00
|
|
|
k.trace(cc);
|
|
|
|
}
|
|
|
|
}
|
2021-04-09 23:15:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Avm2ConstructorRegistry<'_> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Avm2ConstructorRegistry<'gc> {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
2021-04-23 02:06:38 +00:00
|
|
|
constr_map: WeakValueHashMap::new(),
|
2021-04-09 23:15:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-23 02:06:38 +00:00
|
|
|
/// Retrieve the library symbol for a given AVM2 class constructor.
|
2021-04-09 23:15:22 +00:00
|
|
|
///
|
|
|
|
/// A value of `None` indicates that this AVM2 class is not associated with
|
|
|
|
/// a library symbol.
|
2021-04-23 02:06:38 +00:00
|
|
|
pub fn constr_symbol(&self, proto: Avm2Object<'gc>) -> Option<(Arc<SwfMovie>, CharacterId)> {
|
|
|
|
match self.constr_map.get(&proto) {
|
2021-04-10 00:32:04 +00:00
|
|
|
Some(MovieSymbol(movie, symbol)) => Some((movie, symbol)),
|
|
|
|
None => None,
|
|
|
|
}
|
2021-04-09 23:15:22 +00:00
|
|
|
}
|
|
|
|
|
2021-04-23 02:06:38 +00:00
|
|
|
/// Associate an AVM2 class constructor with a given library symbol.
|
|
|
|
pub fn set_constr_symbol(
|
2021-04-10 00:32:04 +00:00
|
|
|
&mut self,
|
|
|
|
proto: Avm2Object<'gc>,
|
|
|
|
movie: Arc<SwfMovie>,
|
|
|
|
symbol: CharacterId,
|
|
|
|
) {
|
2021-04-23 02:06:38 +00:00
|
|
|
self.constr_map.insert(proto, MovieSymbol(movie, symbol));
|
2021-04-09 23:15:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-14 02:41:38 +00:00
|
|
|
/// Symbol library for a single given SWF.
|
2020-05-19 02:12:08 +00:00
|
|
|
#[derive(Collect)]
|
|
|
|
#[collect(no_drop)]
|
2019-11-14 02:41:38 +00:00
|
|
|
pub struct MovieLibrary<'gc> {
|
2019-05-24 17:25:03 +00:00
|
|
|
characters: HashMap<CharacterId, Character<'gc>>,
|
2021-05-03 19:15:59 +00:00
|
|
|
export_characters: Avm1PropertyMap<Character<'gc>>,
|
2019-05-03 00:17:02 +00:00
|
|
|
jpeg_tables: Option<Vec<u8>>,
|
2020-05-19 02:12:08 +00:00
|
|
|
fonts: HashMap<FontDescriptor, Font<'gc>>,
|
2020-10-06 01:41:51 +00:00
|
|
|
avm_type: AvmType,
|
2020-10-09 01:54:51 +00:00
|
|
|
avm2_domain: Option<Avm2Domain<'gc>>,
|
2021-04-09 23:15:22 +00:00
|
|
|
|
2021-01-10 15:45:54 +00:00
|
|
|
/// 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>>>,
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
|
|
|
|
2019-11-14 02:41:38 +00:00
|
|
|
impl<'gc> MovieLibrary<'gc> {
|
2020-10-06 01:41:51 +00:00
|
|
|
pub fn new(avm_type: AvmType) -> Self {
|
2019-11-14 02:41:38 +00:00
|
|
|
MovieLibrary {
|
2019-04-25 17:52:22 +00:00
|
|
|
characters: HashMap::new(),
|
2021-05-03 19:15:59 +00:00
|
|
|
export_characters: Avm1PropertyMap::new(),
|
2019-05-03 00:17:02 +00:00
|
|
|
jpeg_tables: None,
|
2020-05-19 02:12:08 +00:00
|
|
|
fonts: HashMap::new(),
|
2020-10-06 01:41:51 +00:00
|
|
|
avm_type,
|
2020-10-07 03:48:43 +00:00
|
|
|
avm2_domain: None,
|
2021-01-10 15:45:54 +00:00
|
|
|
avm1_constructor_registry: None,
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-24 17:25:03 +00:00
|
|
|
pub fn register_character(&mut self, id: CharacterId, character: Character<'gc>) {
|
2019-04-25 17:52:22 +00:00
|
|
|
// TODO(Herschel): What is the behavior if id already exists?
|
2019-07-19 08:32:41 +00:00
|
|
|
if !self.contains_character(id) {
|
2020-05-19 02:12:08 +00:00
|
|
|
if let Character::Font(font) = character.clone() {
|
2021-02-04 19:54:17 +00:00
|
|
|
self.fonts.insert(font.descriptor().clone(), font);
|
2020-05-19 02:12:08 +00:00
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
self.characters.insert(id, character);
|
|
|
|
} else {
|
|
|
|
log::error!("Character ID collision: Tried to register ID {} twice", id);
|
|
|
|
}
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 19:29:32 +00:00
|
|
|
/// Registers an export name for a given character ID.
|
|
|
|
/// This character will then be instantiable from AVM1.
|
2021-01-10 15:45:54 +00:00
|
|
|
pub fn register_export(
|
|
|
|
&mut self,
|
|
|
|
id: CharacterId,
|
|
|
|
export_name: &str,
|
|
|
|
) -> Option<&Character<'gc>> {
|
2019-12-16 19:29:32 +00:00
|
|
|
if let Some(character) = self.characters.get(&id) {
|
2021-01-15 10:44:16 +00:00
|
|
|
self.export_characters
|
|
|
|
.insert(export_name, character.clone(), false);
|
|
|
|
Some(character)
|
2019-12-16 19:29:32 +00:00
|
|
|
} else {
|
|
|
|
log::warn!(
|
|
|
|
"Can't register export {}: Character ID {} doesn't exist",
|
|
|
|
export_name,
|
2021-01-15 10:44:16 +00:00
|
|
|
id,
|
|
|
|
);
|
|
|
|
None
|
2019-12-16 19:29:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-25 17:52:22 +00:00
|
|
|
pub fn contains_character(&self, id: CharacterId) -> bool {
|
|
|
|
self.characters.contains_key(&id)
|
|
|
|
}
|
|
|
|
|
2021-01-13 20:24:45 +00:00
|
|
|
pub fn character_by_id(&self, id: CharacterId) -> Option<&Character<'gc>> {
|
2019-05-12 16:55:48 +00:00
|
|
|
self.characters.get(&id)
|
|
|
|
}
|
|
|
|
|
2021-01-13 20:24:45 +00:00
|
|
|
pub fn character_by_export_name(&self, name: &str) -> Option<&Character<'gc>> {
|
2020-10-21 23:15:09 +00:00
|
|
|
self.export_characters.get(name, false)
|
2019-05-12 16:55:48 +00:00
|
|
|
}
|
|
|
|
|
2021-01-13 20:24:45 +00:00
|
|
|
pub fn avm1_constructor_registry(&self) -> Option<Gc<'gc, Avm1ConstructorRegistry<'gc>>> {
|
2021-01-10 15:45:54 +00:00
|
|
|
self.avm1_constructor_registry
|
|
|
|
}
|
|
|
|
|
2019-12-17 07:43:30 +00:00
|
|
|
/// Instantiates the library item with the given character ID into a display object.
|
2020-01-20 21:38:23 +00:00
|
|
|
/// The object must then be post-instantiated before being used.
|
2019-12-17 11:25:52 +00:00
|
|
|
pub fn instantiate_by_id(
|
2019-04-25 17:52:22 +00:00
|
|
|
&self,
|
|
|
|
id: CharacterId,
|
2019-05-24 17:25:03 +00:00
|
|
|
gc_context: MutationContext<'gc, '_>,
|
2019-12-07 02:29:36 +00:00
|
|
|
) -> Result<DisplayObject<'gc>, Box<dyn std::error::Error>> {
|
2019-12-17 07:43:30 +00:00
|
|
|
if let Some(character) = self.characters.get(&id) {
|
2020-01-20 21:38:23 +00:00
|
|
|
self.instantiate_display_object(character, gc_context)
|
2019-12-17 07:43:30 +00:00
|
|
|
} else {
|
|
|
|
log::error!("Tried to instantiate non-registered character ID {}", id);
|
|
|
|
Err("Character id doesn't exist".into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Instantiates the library item with the given export name into a display object.
|
2020-01-20 21:38:23 +00:00
|
|
|
/// The object must then be post-instantiated before being used.
|
2019-12-17 07:43:30 +00:00
|
|
|
pub fn instantiate_by_export_name(
|
|
|
|
&self,
|
|
|
|
export_name: &str,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<DisplayObject<'gc>, Box<dyn std::error::Error>> {
|
2020-10-21 23:15:09 +00:00
|
|
|
if let Some(character) = self.export_characters.get(export_name, false) {
|
2020-01-20 21:38:23 +00:00
|
|
|
self.instantiate_display_object(character, gc_context)
|
2019-12-17 07:43:30 +00:00
|
|
|
} else {
|
|
|
|
log::error!(
|
|
|
|
"Tried to instantiate non-registered character {}",
|
|
|
|
export_name
|
|
|
|
);
|
|
|
|
Err("Character id doesn't exist".into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Instantiates the given character into a display object.
|
2020-01-20 21:38:23 +00:00
|
|
|
/// The object must then be post-instantiated before being used.
|
2019-12-17 07:43:30 +00:00
|
|
|
fn instantiate_display_object(
|
|
|
|
&self,
|
|
|
|
character: &Character<'gc>,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<DisplayObject<'gc>, Box<dyn std::error::Error>> {
|
2020-01-20 21:38:23 +00:00
|
|
|
match character {
|
|
|
|
Character::Bitmap(bitmap) => Ok(bitmap.instantiate(gc_context)),
|
|
|
|
Character::EditText(edit_text) => Ok(edit_text.instantiate(gc_context)),
|
|
|
|
Character::Graphic(graphic) => Ok(graphic.instantiate(gc_context)),
|
|
|
|
Character::MorphShape(morph_shape) => Ok(morph_shape.instantiate(gc_context)),
|
|
|
|
Character::MovieClip(movie_clip) => Ok(movie_clip.instantiate(gc_context)),
|
|
|
|
Character::Button(button) => Ok(button.instantiate(gc_context)),
|
|
|
|
Character::Text(text) => Ok(text.instantiate(gc_context)),
|
2020-12-23 02:16:33 +00:00
|
|
|
Character::Video(video) => Ok(video.instantiate(gc_context)),
|
2020-01-20 21:38:23 +00:00
|
|
|
_ => Err("Not a DisplayObject".into()),
|
|
|
|
}
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
2019-05-03 00:17:02 +00:00
|
|
|
|
2020-12-30 23:35:43 +00:00
|
|
|
pub fn get_bitmap(&self, id: CharacterId) -> Option<Bitmap<'gc>> {
|
|
|
|
if let Some(&Character::Bitmap(bitmap)) = self.characters.get(&id) {
|
|
|
|
Some(bitmap)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-17 05:21:59 +00:00
|
|
|
pub fn get_font(&self, id: CharacterId) -> Option<Font<'gc>> {
|
|
|
|
if let Some(&Character::Font(font)) = self.characters.get(&id) {
|
2019-05-04 18:45:11 +00:00
|
|
|
Some(font)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 02:12:08 +00:00
|
|
|
/// Find a font by it's name and parameters.
|
|
|
|
pub fn get_font_by_name(
|
|
|
|
&self,
|
|
|
|
name: &str,
|
|
|
|
is_bold: bool,
|
|
|
|
is_italic: bool,
|
|
|
|
) -> Option<Font<'gc>> {
|
|
|
|
let descriptor = FontDescriptor::from_parts(name, is_bold, is_italic);
|
|
|
|
|
|
|
|
self.fonts.get(&descriptor).copied()
|
2020-05-16 17:04:35 +00:00
|
|
|
}
|
|
|
|
|
2019-05-05 22:55:27 +00:00
|
|
|
pub fn get_sound(&self, id: CharacterId) -> Option<SoundHandle> {
|
|
|
|
if let Some(Character::Sound(sound)) = self.characters.get(&id) {
|
|
|
|
Some(*sound)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-03 00:17:02 +00:00
|
|
|
pub fn set_jpeg_tables(&mut self, data: Vec<u8>) {
|
2019-11-11 21:37:22 +00:00
|
|
|
if self.jpeg_tables.is_some() {
|
|
|
|
// SWF spec says there should only be one JPEGTables tag.
|
|
|
|
// TODO: What is the behavior when there are multiples?
|
|
|
|
log::warn!("SWF contains multiple JPEGTables tags");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Some SWFs have a JPEGTables tag with 0 length; ignore these.
|
|
|
|
// (Does this happen when there is only a single DefineBits tag?)
|
|
|
|
self.jpeg_tables = if data.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(crate::backend::render::remove_invalid_jpeg_data(&data[..]).to_vec())
|
|
|
|
}
|
2019-05-03 00:17:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn jpeg_tables(&self) -> Option<&[u8]> {
|
|
|
|
self.jpeg_tables.as_ref().map(|data| &data[..])
|
|
|
|
}
|
2019-05-24 17:25:03 +00:00
|
|
|
|
2020-10-05 22:46:36 +00:00
|
|
|
/// Check if the current movie's VM type is compatible with running code on
|
|
|
|
/// a particular VM. If it is not, then this yields an error.
|
|
|
|
pub fn check_avm_type(&mut self, new_type: AvmType) -> Result<(), Error> {
|
2020-10-06 01:41:51 +00:00
|
|
|
if self.avm_type != new_type {
|
2020-08-07 02:46:53 +00:00
|
|
|
return Err(format!(
|
|
|
|
"Blocked attempt to run {:?} code on an {:?} movie.",
|
2020-10-06 01:41:51 +00:00
|
|
|
new_type, self.avm_type
|
2020-08-07 02:46:53 +00:00
|
|
|
)
|
|
|
|
.into());
|
|
|
|
}
|
|
|
|
|
2020-10-06 01:41:51 +00:00
|
|
|
self.avm_type = new_type;
|
2020-08-07 02:46:53 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-08-08 00:39:15 +00:00
|
|
|
|
2020-10-06 01:41:51 +00:00
|
|
|
/// Get the VM type of this movie.
|
|
|
|
pub fn avm_type(&self) -> AvmType {
|
2020-10-05 22:46:36 +00:00
|
|
|
self.avm_type
|
2020-08-08 00:39:15 +00:00
|
|
|
}
|
2020-10-07 03:48:43 +00:00
|
|
|
|
2021-03-04 02:11:23 +00:00
|
|
|
/// Forcibly set the AVM type of this movie.
|
|
|
|
///
|
|
|
|
/// This is intended for display object types which can be created
|
|
|
|
/// dynamically but need a placeholder movie. You should *not* attempt to
|
|
|
|
/// change the AVM type of an actual SWF.
|
|
|
|
pub fn force_avm_type(&mut self, new_type: AvmType) {
|
|
|
|
self.avm_type = new_type;
|
|
|
|
}
|
|
|
|
|
2020-10-09 01:54:51 +00:00
|
|
|
pub fn set_avm2_domain(&mut self, avm2_domain: Avm2Domain<'gc>) {
|
2020-10-07 03:48:43 +00:00
|
|
|
self.avm2_domain = Some(avm2_domain);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the AVM2 domain this movie runs under.
|
|
|
|
///
|
|
|
|
/// Note that the presence of an AVM2 domain does *not* indicate that this
|
|
|
|
/// movie provides AVM2 code. For example, a movie may have been loaded by
|
|
|
|
/// AVM2 code into a particular domain, even though it turned out to be
|
|
|
|
/// an AVM1 movie, and thus this domain is unused.
|
2020-10-09 01:54:51 +00:00
|
|
|
pub fn avm2_domain(&self) -> Avm2Domain<'gc> {
|
2020-10-07 03:48:43 +00:00
|
|
|
self.avm2_domain.unwrap()
|
|
|
|
}
|
2019-05-24 17:25:03 +00:00
|
|
|
}
|
|
|
|
|
2019-11-14 02:41:38 +00:00
|
|
|
/// Symbol library for multiple movies.
|
|
|
|
pub struct Library<'gc> {
|
|
|
|
/// All the movie libraries.
|
|
|
|
movie_libraries: PtrWeakKeyHashMap<Weak<SwfMovie>, MovieLibrary<'gc>>,
|
2020-12-18 06:48:41 +00:00
|
|
|
|
|
|
|
/// The embedded device font.
|
|
|
|
device_font: Option<Font<'gc>>,
|
2021-01-10 15:45:54 +00:00
|
|
|
|
|
|
|
constructor_registry_case_insensitive: Gc<'gc, Avm1ConstructorRegistry<'gc>>,
|
|
|
|
constructor_registry_case_sensitive: Gc<'gc, Avm1ConstructorRegistry<'gc>>,
|
2021-04-09 23:44:43 +00:00
|
|
|
|
|
|
|
/// A list of the symbols associated with specific AVM2 constructor
|
|
|
|
/// prototypes.
|
|
|
|
avm2_constructor_registry: Avm2ConstructorRegistry<'gc>,
|
2019-11-14 02:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl<'gc> gc_arena::Collect for Library<'gc> {
|
|
|
|
#[inline]
|
|
|
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
|
|
|
for (_, val) in self.movie_libraries.iter() {
|
|
|
|
val.trace(cc);
|
|
|
|
}
|
2020-12-18 06:48:41 +00:00
|
|
|
self.device_font.trace(cc);
|
2021-01-10 15:45:54 +00:00
|
|
|
self.constructor_registry_case_insensitive.trace(cc);
|
|
|
|
self.constructor_registry_case_sensitive.trace(cc);
|
2021-04-09 23:44:43 +00:00
|
|
|
self.avm2_constructor_registry.trace(cc);
|
2019-11-14 02:41:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Library<'gc> {
|
2021-01-10 15:45:54 +00:00
|
|
|
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),
|
|
|
|
),
|
2021-04-09 23:44:43 +00:00
|
|
|
avm2_constructor_registry: Default::default(),
|
2021-01-10 15:45:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-14 02:41:38 +00:00
|
|
|
pub fn library_for_movie(&self, movie: Arc<SwfMovie>) -> Option<&MovieLibrary<'gc>> {
|
|
|
|
self.movie_libraries.get(&movie)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn library_for_movie_mut(&mut self, movie: Arc<SwfMovie>) -> &mut MovieLibrary<'gc> {
|
|
|
|
if !self.movie_libraries.contains_key(&movie) {
|
2020-10-06 01:41:51 +00:00
|
|
|
let slice = SwfSlice::from(movie.clone());
|
|
|
|
let mut reader = slice.read_from(0);
|
2021-01-10 15:45:54 +00:00
|
|
|
let movie_version = movie.header().version;
|
|
|
|
let vm_type = if movie_version > 8 {
|
2020-10-06 02:25:50 +00:00
|
|
|
match reader.read_tag_code_and_length() {
|
|
|
|
Ok((tag_code, _tag_len))
|
|
|
|
if TagCode::from_u16(tag_code) == Some(TagCode::FileAttributes) =>
|
|
|
|
{
|
|
|
|
match reader.read_file_attributes() {
|
|
|
|
Ok(attributes) if attributes.is_action_script_3 => AvmType::Avm2,
|
|
|
|
Ok(_) => AvmType::Avm1,
|
|
|
|
Err(e) => {
|
2020-10-11 02:27:42 +00:00
|
|
|
log::error!("Got {} when reading FileAttributes", e);
|
2020-10-06 02:25:50 +00:00
|
|
|
AvmType::Avm1
|
|
|
|
}
|
2020-10-06 02:05:11 +00:00
|
|
|
}
|
2020-10-06 01:41:51 +00:00
|
|
|
}
|
2020-10-11 02:27:42 +00:00
|
|
|
// SWF defaults to AVM1 if FileAttributes is not the first tag.
|
2020-10-06 02:25:50 +00:00
|
|
|
_ => AvmType::Avm1,
|
2020-10-06 02:05:11 +00:00
|
|
|
}
|
2020-10-06 02:25:50 +00:00
|
|
|
} else {
|
|
|
|
AvmType::Avm1
|
2020-10-06 02:05:11 +00:00
|
|
|
};
|
2020-10-06 01:41:51 +00:00
|
|
|
|
2021-01-10 15:45:54 +00:00
|
|
|
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);
|
2019-11-14 02:41:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
self.movie_libraries.get_mut(&movie).unwrap()
|
|
|
|
}
|
2020-12-18 06:48:41 +00:00
|
|
|
|
|
|
|
/// Returns the device font for use when a font is unavailable.
|
|
|
|
pub fn device_font(&self) -> Option<Font<'gc>> {
|
|
|
|
self.device_font
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the device font.
|
|
|
|
pub fn set_device_font(&mut self, font: Option<Font<'gc>>) {
|
|
|
|
self.device_font = font;
|
|
|
|
}
|
2019-11-14 02:41:38 +00:00
|
|
|
|
2021-01-10 15:45:54 +00:00
|
|
|
/// 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
|
2019-11-14 02:41:38 +00:00
|
|
|
}
|
|
|
|
}
|
2021-04-09 23:44:43 +00:00
|
|
|
|
|
|
|
/// Get the AVM2 constructor registry.
|
|
|
|
pub fn avm2_constructor_registry(&self) -> &Avm2ConstructorRegistry<'gc> {
|
|
|
|
&self.avm2_constructor_registry
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Mutate the AVM2 constructor registry.
|
|
|
|
pub fn avm2_constructor_registry_mut(&mut self) -> &mut Avm2ConstructorRegistry<'gc> {
|
|
|
|
&mut self.avm2_constructor_registry
|
|
|
|
}
|
2019-11-14 02:41:38 +00:00
|
|
|
}
|