2019-10-17 02:31:41 +00:00
|
|
|
use crate::avm1::globals::SystemPrototypes;
|
2019-12-06 18:24:36 +00:00
|
|
|
use crate::avm1::Object;
|
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;
|
2019-12-07 02:29:36 +00:00
|
|
|
use crate::display_object::TDisplayObject;
|
2019-05-04 18:45:11 +00:00
|
|
|
use crate::font::Font;
|
2019-05-12 16:55:48 +00:00
|
|
|
use crate::prelude::*;
|
2019-11-14 02:41:38 +00:00
|
|
|
use crate::tag_utils::SwfMovie;
|
2019-12-07 02:29:36 +00:00
|
|
|
use gc_arena::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};
|
2019-04-25 17:52:22 +00:00
|
|
|
use swf::CharacterId;
|
2019-11-14 02:41:38 +00:00
|
|
|
use weak_table::PtrWeakKeyHashMap;
|
2019-04-25 17:52:22 +00:00
|
|
|
|
2019-11-14 02:41:38 +00:00
|
|
|
/// Symbol library for a single given SWF.
|
|
|
|
pub struct MovieLibrary<'gc> {
|
2019-05-24 17:25:03 +00:00
|
|
|
characters: HashMap<CharacterId, Character<'gc>>,
|
2019-12-16 19:29:32 +00:00
|
|
|
export_characters: HashMap<String, Character<'gc>>,
|
2019-05-03 00:17:02 +00:00
|
|
|
jpeg_tables: Option<Vec<u8>>,
|
2019-12-17 05:21:59 +00:00
|
|
|
device_font: Option<Font<'gc>>,
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
|
|
|
|
2019-11-14 02:41:38 +00:00
|
|
|
impl<'gc> MovieLibrary<'gc> {
|
2019-10-26 06:44:12 +00:00
|
|
|
pub fn new() -> Self {
|
2019-11-14 02:41:38 +00:00
|
|
|
MovieLibrary {
|
2019-04-25 17:52:22 +00:00
|
|
|
characters: HashMap::new(),
|
2019-12-16 19:29:32 +00:00
|
|
|
export_characters: HashMap::new(),
|
2019-05-03 00:17:02 +00:00
|
|
|
jpeg_tables: None,
|
2019-10-26 06:44:12 +00:00
|
|
|
device_font: 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) {
|
|
|
|
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.
|
|
|
|
pub fn register_export(&mut self, id: CharacterId, export_name: &str) {
|
|
|
|
use std::collections::hash_map::Entry;
|
|
|
|
if let Some(character) = self.characters.get(&id) {
|
|
|
|
match self.export_characters.entry(export_name.to_string()) {
|
|
|
|
Entry::Vacant(e) => {
|
|
|
|
e.insert(character.clone());
|
|
|
|
}
|
|
|
|
Entry::Occupied(_) => {
|
|
|
|
log::warn!(
|
|
|
|
"Can't register export {}: Export already exists",
|
|
|
|
export_name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::warn!(
|
|
|
|
"Can't register export {}: Character ID {} doesn't exist",
|
|
|
|
export_name,
|
|
|
|
id
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-25 17:52:22 +00:00
|
|
|
pub fn contains_character(&self, id: CharacterId) -> bool {
|
|
|
|
self.characters.contains_key(&id)
|
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
#[allow(dead_code)]
|
2019-12-16 19:29:32 +00:00
|
|
|
pub fn get_character_by_id(&self, id: CharacterId) -> Option<&Character<'gc>> {
|
2019-05-12 16:55:48 +00:00
|
|
|
self.characters.get(&id)
|
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
#[allow(dead_code)]
|
2019-12-16 19:29:32 +00:00
|
|
|
pub fn get_character_by_export_name(&self, name: &str) -> Option<&Character<'gc>> {
|
|
|
|
self.export_characters.get(name)
|
2019-05-12 16:55:48 +00:00
|
|
|
}
|
|
|
|
|
2019-12-17 07:43:30 +00:00
|
|
|
/// Instantiates the library item with the given character ID into a display object.
|
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-10-17 02:31:41 +00:00
|
|
|
prototypes: &SystemPrototypes<'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) {
|
|
|
|
self.instantiate_display_object(character, gc_context, prototypes)
|
|
|
|
} 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.
|
|
|
|
pub fn instantiate_by_export_name(
|
|
|
|
&self,
|
|
|
|
export_name: &str,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
prototypes: &SystemPrototypes<'gc>,
|
|
|
|
) -> Result<DisplayObject<'gc>, Box<dyn std::error::Error>> {
|
|
|
|
if let Some(character) = self.export_characters.get(export_name) {
|
|
|
|
self.instantiate_display_object(character, gc_context, prototypes)
|
|
|
|
} 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.
|
|
|
|
fn instantiate_display_object(
|
|
|
|
&self,
|
|
|
|
character: &Character<'gc>,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
prototypes: &SystemPrototypes<'gc>,
|
|
|
|
) -> Result<DisplayObject<'gc>, Box<dyn std::error::Error>> {
|
|
|
|
let (mut obj, proto): (DisplayObject<'gc>, Object<'gc>) = match character {
|
|
|
|
Character::Bitmap(bitmap) => (bitmap.instantiate(gc_context), prototypes.object),
|
|
|
|
Character::EditText(edit_text) => {
|
2019-12-19 09:31:08 +00:00
|
|
|
(edit_text.instantiate(gc_context), prototypes.text_field)
|
2019-12-07 02:29:36 +00:00
|
|
|
}
|
2019-12-17 07:43:30 +00:00
|
|
|
Character::Graphic(graphic) => (graphic.instantiate(gc_context), prototypes.object),
|
|
|
|
Character::MorphShape(morph_shape) => {
|
2019-12-07 02:29:36 +00:00
|
|
|
(morph_shape.instantiate(gc_context), prototypes.object)
|
|
|
|
}
|
2019-12-17 07:43:30 +00:00
|
|
|
Character::MovieClip(movie_clip) => {
|
2019-12-07 02:29:36 +00:00
|
|
|
(movie_clip.instantiate(gc_context), prototypes.movie_clip)
|
|
|
|
}
|
2019-12-17 07:43:30 +00:00
|
|
|
Character::Button(button) => (button.instantiate(gc_context), prototypes.object),
|
|
|
|
Character::Text(text) => (text.instantiate(gc_context), prototypes.object),
|
|
|
|
_ => return Err("Not a DisplayObject".into()),
|
2019-05-07 10:22:58 +00:00
|
|
|
};
|
2019-12-07 02:29:36 +00:00
|
|
|
obj.post_instantiation(gc_context, obj, proto);
|
|
|
|
Ok(obj)
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
2019-05-03 00:17:02 +00:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2019-10-08 04:02:09 +00:00
|
|
|
/// Returns the device font for use when a font is unavailable.
|
2019-12-17 05:21:59 +00:00
|
|
|
pub fn device_font(&self) -> Option<Font<'gc>> {
|
|
|
|
self.device_font
|
2019-10-26 06:44:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the device font.
|
2019-12-17 05:21:59 +00:00
|
|
|
pub fn set_device_font(&mut self, font: Option<Font<'gc>>) {
|
2019-10-26 06:44:12 +00:00
|
|
|
self.device_font = font;
|
2019-05-24 17:25:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-14 02:41:38 +00:00
|
|
|
unsafe impl<'gc> gc_arena::Collect for MovieLibrary<'gc> {
|
2019-05-24 17:25:03 +00:00
|
|
|
#[inline]
|
|
|
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
|
|
|
for character in self.characters.values() {
|
|
|
|
character.trace(cc);
|
|
|
|
}
|
2019-12-17 05:21:59 +00:00
|
|
|
self.device_font.trace(cc);
|
2019-05-24 17:25:03 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-26 06:44:12 +00:00
|
|
|
|
2019-11-14 02:41:38 +00:00
|
|
|
impl Default for MovieLibrary<'_> {
|
2019-10-26 06:44:12 +00:00
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
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>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Library<'gc> {
|
|
|
|
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) {
|
|
|
|
self.movie_libraries
|
|
|
|
.insert(movie.clone(), MovieLibrary::default());
|
|
|
|
};
|
|
|
|
|
|
|
|
self.movie_libraries.get_mut(&movie).unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Default for Library<'gc> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
movie_libraries: PtrWeakKeyHashMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|