2019-08-30 23:25:04 +00:00
|
|
|
use crate::avm1::movie_clip::create_movie_object;
|
2019-08-31 12:09:37 +00:00
|
|
|
use crate::avm1::object::{Object, TYPE_OF_MOVIE_CLIP};
|
2019-08-31 16:28:28 +00:00
|
|
|
use crate::avm1::Value;
|
2019-08-09 23:03:13 +00:00
|
|
|
use crate::backend::audio::AudioStreamHandle;
|
2019-04-29 05:55:44 +00:00
|
|
|
use crate::character::Character;
|
2019-04-26 21:11:29 +00:00
|
|
|
use crate::color_transform::ColorTransform;
|
2019-05-24 17:25:03 +00:00
|
|
|
use crate::display_object::{DisplayObject, DisplayObjectBase};
|
2019-05-04 18:45:11 +00:00
|
|
|
use crate::font::Font;
|
2019-05-07 10:22:58 +00:00
|
|
|
use crate::graphic::Graphic;
|
2019-04-25 17:52:22 +00:00
|
|
|
use crate::matrix::Matrix;
|
2019-09-05 05:44:35 +00:00
|
|
|
use crate::morph_shape::MorphShapeStatic;
|
2019-04-27 01:55:06 +00:00
|
|
|
use crate::player::{RenderContext, UpdateContext};
|
2019-05-12 16:55:48 +00:00
|
|
|
use crate::prelude::*;
|
2019-07-19 08:32:41 +00:00
|
|
|
use crate::tag_utils::{self, DecodeResult, SwfStream};
|
2019-05-04 18:45:11 +00:00
|
|
|
use crate::text::Text;
|
2019-08-28 23:29:43 +00:00
|
|
|
use gc_arena::{Gc, GcCell, MutationContext};
|
2019-08-15 18:29:00 +00:00
|
|
|
use std::collections::{BTreeMap, HashMap};
|
2019-05-02 00:46:49 +00:00
|
|
|
use swf::read::SwfRead;
|
2019-04-25 17:52:22 +00:00
|
|
|
|
|
|
|
type Depth = i16;
|
|
|
|
type FrameNumber = u16;
|
|
|
|
|
2019-08-28 23:29:43 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2019-05-24 17:25:03 +00:00
|
|
|
pub struct MovieClip<'gc> {
|
2019-08-13 02:00:12 +00:00
|
|
|
base: DisplayObjectBase<'gc>,
|
2019-08-16 00:00:48 +00:00
|
|
|
static_data: Gc<'gc, MovieClipStatic>,
|
2019-04-26 03:27:44 +00:00
|
|
|
tag_stream_pos: u64,
|
2019-04-25 17:52:22 +00:00
|
|
|
is_playing: bool,
|
2019-05-24 17:25:03 +00:00
|
|
|
goto_queue: Vec<FrameNumber>,
|
2019-04-25 17:52:22 +00:00
|
|
|
current_frame: FrameNumber,
|
2019-04-30 08:53:21 +00:00
|
|
|
audio_stream: Option<AudioStreamHandle>,
|
2019-05-24 17:25:03 +00:00
|
|
|
children: BTreeMap<Depth, DisplayNode<'gc>>,
|
2019-08-28 23:29:43 +00:00
|
|
|
object: GcCell<'gc, Object<'gc>>,
|
|
|
|
}
|
|
|
|
|
2019-05-24 17:25:03 +00:00
|
|
|
impl<'gc> MovieClip<'gc> {
|
2019-08-16 00:00:48 +00:00
|
|
|
pub fn new(gc_context: MutationContext<'gc, '_>) -> Self {
|
2019-05-24 17:25:03 +00:00
|
|
|
Self {
|
2019-04-29 05:55:44 +00:00
|
|
|
base: Default::default(),
|
2019-08-16 00:00:48 +00:00
|
|
|
static_data: Gc::allocate(gc_context, MovieClipStatic::default()),
|
2019-04-26 03:27:44 +00:00
|
|
|
tag_stream_pos: 0,
|
2019-08-22 05:35:32 +00:00
|
|
|
is_playing: false,
|
2019-05-24 17:25:03 +00:00
|
|
|
goto_queue: Vec::new(),
|
2019-04-26 03:27:44 +00:00
|
|
|
current_frame: 0,
|
2019-04-30 08:53:21 +00:00
|
|
|
audio_stream: None,
|
2019-05-03 21:25:01 +00:00
|
|
|
children: BTreeMap::new(),
|
2019-08-31 15:54:15 +00:00
|
|
|
object: GcCell::allocate(gc_context, create_movie_object(gc_context)),
|
2019-04-29 05:55:44 +00:00
|
|
|
}
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
|
|
|
|
2019-07-25 07:58:34 +00:00
|
|
|
pub fn new_with_data(
|
2019-08-16 00:00:48 +00:00
|
|
|
gc_context: MutationContext<'gc, '_>,
|
2019-07-25 07:58:34 +00:00
|
|
|
id: CharacterId,
|
|
|
|
tag_stream_start: u64,
|
|
|
|
tag_stream_len: usize,
|
|
|
|
num_frames: u16,
|
|
|
|
) -> Self {
|
2019-05-24 17:25:03 +00:00
|
|
|
Self {
|
2019-04-29 05:55:44 +00:00
|
|
|
base: Default::default(),
|
2019-08-19 14:53:12 +00:00
|
|
|
static_data: Gc::allocate(
|
|
|
|
gc_context,
|
|
|
|
MovieClipStatic {
|
|
|
|
id,
|
|
|
|
tag_stream_start,
|
|
|
|
tag_stream_len,
|
|
|
|
total_frames: num_frames,
|
|
|
|
audio_stream_info: None,
|
|
|
|
frame_labels: HashMap::new(),
|
|
|
|
},
|
|
|
|
),
|
2019-07-19 08:32:41 +00:00
|
|
|
tag_stream_pos: 0,
|
2019-04-25 17:52:22 +00:00
|
|
|
is_playing: true,
|
2019-05-24 17:25:03 +00:00
|
|
|
goto_queue: Vec::new(),
|
2019-04-26 03:27:44 +00:00
|
|
|
current_frame: 0,
|
2019-04-30 08:53:21 +00:00
|
|
|
audio_stream: None,
|
2019-05-03 21:25:01 +00:00
|
|
|
children: BTreeMap::new(),
|
2019-08-31 15:54:15 +00:00
|
|
|
object: GcCell::allocate(gc_context, create_movie_object(gc_context)),
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-02 00:46:49 +00:00
|
|
|
pub fn playing(&self) -> bool {
|
|
|
|
self.is_playing
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn next_frame(&mut self) {
|
2019-08-16 00:00:48 +00:00
|
|
|
if self.current_frame() < self.total_frames() {
|
2019-05-02 00:46:49 +00:00
|
|
|
self.goto_frame(self.current_frame + 1, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn play(&mut self) {
|
2019-08-22 05:35:32 +00:00
|
|
|
// Can only play clips with multiple frames.
|
|
|
|
if self.total_frames() > 1 {
|
|
|
|
self.is_playing = true;
|
|
|
|
}
|
2019-05-02 00:46:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn prev_frame(&mut self) {
|
|
|
|
if self.current_frame > 1 {
|
|
|
|
self.goto_frame(self.current_frame - 1, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn stop(&mut self) {
|
|
|
|
self.is_playing = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn goto_frame(&mut self, frame: FrameNumber, stop: bool) {
|
2019-08-15 18:29:00 +00:00
|
|
|
if frame != self.current_frame {
|
|
|
|
self.goto_queue.push(frame);
|
|
|
|
}
|
2019-05-02 00:46:49 +00:00
|
|
|
|
|
|
|
if stop {
|
|
|
|
self.stop();
|
|
|
|
} else {
|
|
|
|
self.play();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-07 06:31:34 +00:00
|
|
|
pub fn x(&self) -> f32 {
|
2019-08-15 22:04:47 +00:00
|
|
|
self.matrix().tx / Twips::TWIPS_PER_PIXEL as f32
|
2019-05-07 06:31:34 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 21:42:45 +00:00
|
|
|
pub fn set_x(&mut self, val: f32) {
|
2019-08-15 22:04:47 +00:00
|
|
|
self.matrix_mut().tx = val * Twips::TWIPS_PER_PIXEL as f32;
|
2019-08-15 21:42:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-07 06:31:34 +00:00
|
|
|
pub fn y(&self) -> f32 {
|
2019-08-15 22:04:47 +00:00
|
|
|
self.matrix().ty / Twips::TWIPS_PER_PIXEL as f32
|
2019-05-07 06:31:34 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 21:42:45 +00:00
|
|
|
pub fn set_y(&mut self, val: f32) {
|
2019-08-15 22:04:47 +00:00
|
|
|
self.matrix_mut().ty = val * Twips::TWIPS_PER_PIXEL as f32;
|
2019-08-15 21:42:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-07 06:31:34 +00:00
|
|
|
pub fn x_scale(&self) -> f32 {
|
2019-08-14 18:59:07 +00:00
|
|
|
self.matrix().a * 100.0
|
2019-05-07 06:31:34 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 21:42:45 +00:00
|
|
|
pub fn set_x_scale(&mut self, val: f32) {
|
|
|
|
self.matrix_mut().a = val / 100.0;
|
|
|
|
}
|
|
|
|
|
2019-05-07 06:31:34 +00:00
|
|
|
pub fn y_scale(&self) -> f32 {
|
2019-08-14 18:59:07 +00:00
|
|
|
self.matrix().d * 100.0
|
2019-05-07 06:31:34 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 21:42:45 +00:00
|
|
|
pub fn set_y_scale(&mut self, val: f32) {
|
|
|
|
self.matrix_mut().d = val / 100.0;
|
|
|
|
}
|
|
|
|
|
2019-05-07 06:31:34 +00:00
|
|
|
pub fn current_frame(&self) -> FrameNumber {
|
|
|
|
self.current_frame
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn total_frames(&self) -> FrameNumber {
|
2019-08-16 00:00:48 +00:00
|
|
|
self.static_data.total_frames
|
2019-05-07 06:31:34 +00:00
|
|
|
}
|
|
|
|
|
2019-08-17 01:03:02 +00:00
|
|
|
pub fn rotation(&self) -> f32 {
|
|
|
|
// TODO: Cache the user-friendly transform values like rotation.
|
|
|
|
let matrix = self.matrix();
|
|
|
|
f32::atan2(matrix.b, matrix.a).to_degrees()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_rotation(&mut self, degrees: f32) {
|
|
|
|
// TODO: Use cached user-friendly transform values.
|
|
|
|
let angle = degrees.to_radians();
|
|
|
|
let cos = f32::cos(angle);
|
|
|
|
let sin = f32::sin(angle);
|
|
|
|
let scale_x = self.x_scale() / 100.0;
|
|
|
|
let scale_y = self.y_scale() / 100.0;
|
|
|
|
|
|
|
|
let matrix = self.matrix_mut();
|
|
|
|
*matrix = Matrix {
|
|
|
|
a: scale_x * cos,
|
|
|
|
b: scale_x * sin,
|
|
|
|
c: scale_y * cos,
|
|
|
|
d: -scale_y * sin,
|
|
|
|
tx: matrix.tx,
|
2019-08-19 14:53:12 +00:00
|
|
|
ty: matrix.ty,
|
2019-08-17 01:03:02 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-05-07 06:31:34 +00:00
|
|
|
pub fn frames_loaded(&self) -> FrameNumber {
|
|
|
|
// TODO(Herschel): root needs to progressively stream in frames.
|
2019-08-16 00:00:48 +00:00
|
|
|
self.static_data.total_frames
|
2019-05-07 06:31:34 +00:00
|
|
|
}
|
|
|
|
|
2019-05-24 17:25:03 +00:00
|
|
|
pub fn get_child_by_name(&self, name: &str) -> Option<&DisplayNode<'gc>> {
|
2019-08-15 18:29:00 +00:00
|
|
|
// TODO: Make a HashMap from name -> child?
|
2019-05-07 06:31:34 +00:00
|
|
|
self.children
|
|
|
|
.values()
|
2019-05-24 17:25:03 +00:00
|
|
|
.find(|child| child.read().name() == name)
|
2019-05-07 06:31:34 +00:00
|
|
|
}
|
|
|
|
|
2019-08-19 14:53:12 +00:00
|
|
|
pub fn frame_label_to_number(&self, frame_label: &str) -> Option<FrameNumber> {
|
2019-08-16 00:00:48 +00:00
|
|
|
self.static_data.frame_labels.get(frame_label).copied()
|
2019-05-07 06:31:34 +00:00
|
|
|
}
|
|
|
|
|
2019-05-24 17:25:03 +00:00
|
|
|
pub fn run_goto_queue(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
2019-05-02 00:46:49 +00:00
|
|
|
let mut i = 0;
|
|
|
|
while i < self.goto_queue.len() {
|
|
|
|
let frame = self.goto_queue[i];
|
|
|
|
if frame >= self.current_frame {
|
|
|
|
// Advancing
|
|
|
|
while self.current_frame + 1 < frame {
|
|
|
|
self.run_frame_internal(context, true);
|
|
|
|
}
|
|
|
|
self.run_frame_internal(context, false);
|
|
|
|
} else {
|
|
|
|
// Rewind
|
|
|
|
// Reset everything to blank, start from frame 1,
|
|
|
|
// and advance forward
|
|
|
|
self.children.clear();
|
2019-07-19 08:32:41 +00:00
|
|
|
self.tag_stream_pos = 0;
|
2019-05-02 00:46:49 +00:00
|
|
|
self.current_frame = 0;
|
|
|
|
while self.current_frame + 1 < frame {
|
|
|
|
self.run_frame_internal(context, true);
|
|
|
|
}
|
|
|
|
self.run_frame_internal(context, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.goto_queue.clear();
|
|
|
|
}
|
|
|
|
|
2019-08-16 01:00:53 +00:00
|
|
|
pub fn id(&self) -> CharacterId {
|
2019-08-16 00:00:48 +00:00
|
|
|
self.static_data.id
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tag_stream_start(&self) -> u64 {
|
|
|
|
self.static_data.tag_stream_start
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tag_stream_len(&self) -> usize {
|
|
|
|
self.static_data.tag_stream_len
|
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
fn reader<'a>(
|
|
|
|
&self,
|
|
|
|
context: &UpdateContext<'a, '_, '_>,
|
|
|
|
) -> swf::read::Reader<std::io::Cursor<&'a [u8]>> {
|
|
|
|
let mut cursor = std::io::Cursor::new(
|
2019-08-16 00:00:48 +00:00
|
|
|
&context.swf_data[self.tag_stream_start() as usize
|
|
|
|
..self.tag_stream_start() as usize + self.tag_stream_len()],
|
2019-07-19 08:32:41 +00:00
|
|
|
);
|
|
|
|
cursor.set_position(self.tag_stream_pos);
|
|
|
|
swf::read::Reader::new(cursor, context.swf_version)
|
|
|
|
}
|
|
|
|
fn run_frame_internal(
|
2019-05-24 17:25:03 +00:00
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-07-19 08:32:41 +00:00
|
|
|
only_display_actions: bool,
|
2019-05-24 17:25:03 +00:00
|
|
|
) {
|
2019-07-19 08:32:41 +00:00
|
|
|
// Advance frame number.
|
2019-08-16 00:00:48 +00:00
|
|
|
if self.current_frame() < self.total_frames() {
|
2019-07-19 08:32:41 +00:00
|
|
|
self.current_frame += 1;
|
2019-08-22 05:35:32 +00:00
|
|
|
} else if self.total_frames() > 1 {
|
2019-07-19 08:32:41 +00:00
|
|
|
self.current_frame = 1;
|
|
|
|
self.children.clear();
|
|
|
|
self.tag_stream_pos = 0;
|
2019-08-22 05:35:32 +00:00
|
|
|
} else {
|
|
|
|
// Single frame clips do not play.
|
|
|
|
self.stop();
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let _tag_pos = self.tag_stream_pos;
|
|
|
|
let mut reader = self.reader(context);
|
|
|
|
|
|
|
|
use swf::TagCode;
|
|
|
|
if only_display_actions {
|
2019-08-16 17:03:16 +00:00
|
|
|
let tag_callback = |reader: &mut _, tag_code, tag_len| match tag_code {
|
|
|
|
TagCode::PlaceObject => self.place_object(context, reader, tag_len, 1),
|
|
|
|
TagCode::PlaceObject2 => self.place_object(context, reader, tag_len, 2),
|
|
|
|
TagCode::PlaceObject3 => self.place_object(context, reader, tag_len, 3),
|
|
|
|
TagCode::PlaceObject4 => self.place_object(context, reader, tag_len, 4),
|
2019-07-19 08:32:41 +00:00
|
|
|
TagCode::RemoveObject => self.remove_object(context, reader, 1),
|
|
|
|
TagCode::RemoveObject2 => self.remove_object(context, reader, 2),
|
|
|
|
_ => Ok(()),
|
|
|
|
};
|
|
|
|
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame);
|
|
|
|
} else {
|
2019-07-25 07:58:34 +00:00
|
|
|
let tag_callback = |reader: &mut _, tag_code, tag_len| match tag_code {
|
|
|
|
TagCode::DoAction => self.do_action(context, reader, tag_len),
|
2019-08-16 17:03:16 +00:00
|
|
|
TagCode::PlaceObject => self.place_object(context, reader, tag_len, 1),
|
|
|
|
TagCode::PlaceObject2 => self.place_object(context, reader, tag_len, 2),
|
|
|
|
TagCode::PlaceObject3 => self.place_object(context, reader, tag_len, 3),
|
|
|
|
TagCode::PlaceObject4 => self.place_object(context, reader, tag_len, 4),
|
2019-07-19 08:32:41 +00:00
|
|
|
TagCode::RemoveObject => self.remove_object(context, reader, 1),
|
|
|
|
TagCode::RemoveObject2 => self.remove_object(context, reader, 2),
|
|
|
|
TagCode::SetBackgroundColor => self.set_background_color(context, reader),
|
|
|
|
TagCode::StartSound => self.start_sound_1(context, reader),
|
|
|
|
TagCode::SoundStreamBlock => self.sound_stream_block(context, reader),
|
|
|
|
_ => Ok(()),
|
|
|
|
};
|
|
|
|
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.tag_stream_pos = reader.get_ref().position();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
|
|
|
|
impl_display_object!(base);
|
|
|
|
|
|
|
|
fn run_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
|
|
|
if self.is_playing {
|
|
|
|
self.run_frame_internal(context, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(Herschel): Verify order of execution for parent/children.
|
|
|
|
// Parent first? Children first? Sorted by depth?
|
|
|
|
for child in self.children.values_mut() {
|
2019-08-13 02:00:12 +00:00
|
|
|
context.active_clip = *child;
|
2019-07-19 08:32:41 +00:00
|
|
|
child.write(context.gc_context).run_frame(context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run_post_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
|
|
|
self.run_goto_queue(context);
|
|
|
|
|
|
|
|
for child in self.children.values() {
|
2019-08-13 02:00:12 +00:00
|
|
|
context.active_clip = *child;
|
2019-07-19 08:32:41 +00:00
|
|
|
child.write(context.gc_context).run_post_frame(context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
|
|
|
|
context.transform_stack.push(self.transform());
|
2019-09-08 02:33:06 +00:00
|
|
|
crate::display_object::render_children(context, &self.children);
|
2019-07-19 08:32:41 +00:00
|
|
|
context.transform_stack.pop();
|
|
|
|
}
|
|
|
|
|
2019-08-19 14:53:12 +00:00
|
|
|
fn mouse_pick(
|
|
|
|
&self,
|
|
|
|
_self_node: DisplayNode<'gc>,
|
|
|
|
point: (Twips, Twips),
|
|
|
|
) -> Option<DisplayNode<'gc>> {
|
2019-08-19 23:11:38 +00:00
|
|
|
for child in self.children.values().rev() {
|
2019-08-19 14:53:12 +00:00
|
|
|
let result = child.read().mouse_pick(*child, point);
|
|
|
|
if result.is_some() {
|
|
|
|
return result;
|
2019-08-14 19:39:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-15 18:29:00 +00:00
|
|
|
None
|
2019-08-14 19:39:26 +00:00
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
fn as_movie_clip(&self) -> Option<&crate::movie_clip::MovieClip<'gc>> {
|
|
|
|
Some(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_movie_clip_mut(&mut self) -> Option<&mut crate::movie_clip::MovieClip<'gc>> {
|
|
|
|
Some(self)
|
|
|
|
}
|
2019-08-28 23:29:43 +00:00
|
|
|
|
|
|
|
fn post_instantiation(
|
|
|
|
&mut self,
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
2019-08-31 12:09:37 +00:00
|
|
|
display_object: DisplayNode<'gc>,
|
2019-08-28 23:29:43 +00:00
|
|
|
) {
|
|
|
|
let mut object = self.object.write(gc_context);
|
2019-08-31 12:09:37 +00:00
|
|
|
object.set_display_node(display_object);
|
|
|
|
object.set_type_of(TYPE_OF_MOVIE_CLIP);
|
2019-08-28 23:29:43 +00:00
|
|
|
}
|
2019-08-31 15:54:15 +00:00
|
|
|
|
|
|
|
fn object(&self) -> Value<'gc> {
|
|
|
|
Value::Object(self.object)
|
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl<'gc> gc_arena::Collect for MovieClip<'gc> {
|
|
|
|
#[inline]
|
|
|
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
|
|
|
for child in self.children.values() {
|
|
|
|
child.trace(cc);
|
|
|
|
}
|
2019-08-23 06:28:51 +00:00
|
|
|
self.base.trace(cc);
|
2019-08-19 23:02:40 +00:00
|
|
|
self.static_data.trace(cc);
|
2019-08-28 23:29:43 +00:00
|
|
|
self.object.trace(cc);
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Preloading of definition tags
|
|
|
|
impl<'gc, 'a> MovieClip<'gc> {
|
2019-09-05 05:44:35 +00:00
|
|
|
pub fn preload(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
morph_shapes: &mut fnv::FnvHashMap<CharacterId, MorphShapeStatic>,
|
|
|
|
) {
|
|
|
|
use swf::TagCode;
|
|
|
|
// TODO: Re-creating static data because preload step occurs after construction.
|
|
|
|
// Should be able to hoist this up somewhere, or use MaybeUninit.
|
|
|
|
let mut static_data = (&*self.static_data).clone();
|
|
|
|
let mut reader = self.reader(context);
|
|
|
|
let mut cur_frame = 1;
|
|
|
|
let mut ids = fnv::FnvHashMap::default();
|
|
|
|
let tag_callback = |reader: &mut _, tag_code, tag_len| match tag_code {
|
|
|
|
TagCode::DefineBits => self.define_bits(context, reader, tag_len),
|
|
|
|
TagCode::DefineBitsJpeg2 => self.define_bits_jpeg_2(context, reader, tag_len),
|
|
|
|
TagCode::DefineBitsJpeg3 => self.define_bits_jpeg_3(context, reader, tag_len),
|
|
|
|
TagCode::DefineBitsJpeg4 => self.define_bits_jpeg_4(context, reader, tag_len),
|
|
|
|
TagCode::DefineBitsLossless => self.define_bits_lossless(context, reader, 1),
|
|
|
|
TagCode::DefineBitsLossless2 => self.define_bits_lossless(context, reader, 2),
|
|
|
|
TagCode::DefineButton => self.define_button_1(context, reader),
|
|
|
|
TagCode::DefineButton2 => self.define_button_2(context, reader),
|
|
|
|
TagCode::DefineFont => self.define_font_1(context, reader),
|
|
|
|
TagCode::DefineFont2 => self.define_font_2(context, reader),
|
|
|
|
TagCode::DefineFont3 => self.define_font_3(context, reader),
|
|
|
|
TagCode::DefineFont4 => unimplemented!(),
|
|
|
|
TagCode::DefineMorphShape => self.define_morph_shape(context, reader, morph_shapes, 1),
|
|
|
|
TagCode::DefineMorphShape2 => self.define_morph_shape(context, reader, morph_shapes, 2),
|
|
|
|
TagCode::DefineShape => self.define_shape(context, reader, 1),
|
|
|
|
TagCode::DefineShape2 => self.define_shape(context, reader, 2),
|
|
|
|
TagCode::DefineShape3 => self.define_shape(context, reader, 3),
|
|
|
|
TagCode::DefineShape4 => self.define_shape(context, reader, 4),
|
|
|
|
TagCode::DefineSound => self.define_sound(context, reader, tag_len),
|
|
|
|
TagCode::DefineSprite => self.define_sprite(context, reader, tag_len, morph_shapes),
|
|
|
|
TagCode::DefineText => self.define_text(context, reader),
|
|
|
|
TagCode::FrameLabel => {
|
|
|
|
self.frame_label(context, reader, tag_len, cur_frame, &mut static_data)
|
|
|
|
}
|
|
|
|
TagCode::JpegTables => self.jpeg_tables(context, reader, tag_len),
|
|
|
|
TagCode::PlaceObject => {
|
|
|
|
self.preload_place_object(context, reader, tag_len, &mut ids, morph_shapes, 1)
|
|
|
|
}
|
|
|
|
TagCode::PlaceObject2 => {
|
|
|
|
self.preload_place_object(context, reader, tag_len, &mut ids, morph_shapes, 2)
|
|
|
|
}
|
|
|
|
TagCode::PlaceObject3 => {
|
|
|
|
self.preload_place_object(context, reader, tag_len, &mut ids, morph_shapes, 3)
|
|
|
|
}
|
|
|
|
TagCode::PlaceObject4 => {
|
|
|
|
self.preload_place_object(context, reader, tag_len, &mut ids, morph_shapes, 4)
|
|
|
|
}
|
|
|
|
TagCode::RemoveObject => self.preload_remove_object(context, reader, &mut ids, 1),
|
|
|
|
TagCode::RemoveObject2 => self.preload_remove_object(context, reader, &mut ids, 2),
|
|
|
|
TagCode::ShowFrame => self.preload_show_frame(context, reader, &mut cur_frame),
|
|
|
|
TagCode::SoundStreamHead => {
|
|
|
|
self.preload_sound_stream_head(context, reader, &mut static_data, 1)
|
|
|
|
}
|
|
|
|
TagCode::SoundStreamHead2 => {
|
|
|
|
self.preload_sound_stream_head(context, reader, &mut static_data, 2)
|
|
|
|
}
|
|
|
|
TagCode::SoundStreamBlock => {
|
|
|
|
self.preload_sound_stream_block(context, reader, &mut static_data, tag_len)
|
|
|
|
}
|
|
|
|
_ => Ok(()),
|
|
|
|
};
|
|
|
|
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::End);
|
|
|
|
self.static_data = Gc::allocate(context.gc_context, static_data);
|
|
|
|
|
|
|
|
// Finalize audio stream.
|
|
|
|
if self.static_data.audio_stream_info.is_some() {
|
|
|
|
context.audio.preload_sound_stream_end(self.id());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
#[inline]
|
|
|
|
fn define_bits_lossless(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
version: u8,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let define_bits_lossless = reader.read_define_bits_lossless(version)?;
|
|
|
|
let handle = context.renderer.register_bitmap_png(&define_bits_lossless);
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(define_bits_lossless.id, Character::Bitmap(handle));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn define_morph_shape(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
2019-09-05 05:44:35 +00:00
|
|
|
morph_shapes: &mut fnv::FnvHashMap<CharacterId, MorphShapeStatic>,
|
2019-07-19 08:32:41 +00:00
|
|
|
version: u8,
|
|
|
|
) -> DecodeResult {
|
2019-09-05 05:44:35 +00:00
|
|
|
// Certain backends may have to preload morph shape frames, so defer registering until the end.
|
2019-07-19 08:32:41 +00:00
|
|
|
let swf_shape = reader.read_define_morph_shape(version)?;
|
2019-09-05 05:44:35 +00:00
|
|
|
let morph_shape = MorphShapeStatic::from_swf_tag(context.renderer, &swf_shape);
|
|
|
|
morph_shapes.insert(swf_shape.id, morph_shape);
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn define_shape(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
version: u8,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let swf_shape = reader.read_define_shape(version)?;
|
2019-08-14 17:19:01 +00:00
|
|
|
let graphic = Graphic::from_swf_tag(context, &swf_shape);
|
2019-07-19 08:32:41 +00:00
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(swf_shape.id, Character::Graphic(Box::new(graphic)));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn preload_place_object(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
2019-08-16 17:03:16 +00:00
|
|
|
tag_len: usize,
|
2019-07-19 08:32:41 +00:00
|
|
|
ids: &mut fnv::FnvHashMap<Depth, CharacterId>,
|
2019-09-05 05:44:35 +00:00
|
|
|
morph_shapes: &mut fnv::FnvHashMap<CharacterId, MorphShapeStatic>,
|
2019-07-19 08:32:41 +00:00
|
|
|
version: u8,
|
|
|
|
) -> DecodeResult {
|
|
|
|
use swf::PlaceObjectAction;
|
|
|
|
let place_object = if version == 1 {
|
2019-08-16 17:03:16 +00:00
|
|
|
reader.read_place_object(tag_len)
|
2019-07-19 08:32:41 +00:00
|
|
|
} else {
|
|
|
|
reader.read_place_object_2_or_3(version)
|
|
|
|
}?;
|
|
|
|
match place_object.action {
|
|
|
|
PlaceObjectAction::Place(id) => {
|
2019-09-05 05:44:35 +00:00
|
|
|
if let Some(morph_shape) = morph_shapes.get_mut(&id) {
|
2019-07-19 08:32:41 +00:00
|
|
|
ids.insert(place_object.depth, id);
|
|
|
|
if let Some(ratio) = place_object.ratio {
|
|
|
|
morph_shape.register_ratio(context.renderer, ratio);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PlaceObjectAction::Modify => {
|
|
|
|
if let Some(&id) = ids.get(&place_object.depth) {
|
2019-09-05 05:44:35 +00:00
|
|
|
if let Some(morph_shape) = morph_shapes.get_mut(&id) {
|
2019-07-19 08:32:41 +00:00
|
|
|
ids.insert(place_object.depth, id);
|
|
|
|
if let Some(ratio) = place_object.ratio {
|
|
|
|
morph_shape.register_ratio(context.renderer, ratio);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PlaceObjectAction::Replace(id) => {
|
2019-09-05 05:44:35 +00:00
|
|
|
if let Some(morph_shape) = morph_shapes.get_mut(&id) {
|
2019-07-19 08:32:41 +00:00
|
|
|
ids.insert(place_object.depth, id);
|
|
|
|
if let Some(ratio) = place_object.ratio {
|
|
|
|
morph_shape.register_ratio(context.renderer, ratio);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ids.remove(&place_object.depth);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2019-07-25 07:58:34 +00:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn preload_sound_stream_block(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
2019-08-20 01:17:29 +00:00
|
|
|
static_data: &mut MovieClipStatic,
|
2019-07-25 07:58:34 +00:00
|
|
|
tag_len: usize,
|
|
|
|
) -> DecodeResult {
|
2019-08-20 01:17:29 +00:00
|
|
|
if static_data.audio_stream_info.is_some() {
|
2019-07-25 07:58:34 +00:00
|
|
|
let pos = reader.get_ref().position() as usize;
|
|
|
|
let data = reader.get_ref().get_ref();
|
|
|
|
let data = &data[pos..pos + tag_len];
|
2019-08-16 00:00:48 +00:00
|
|
|
context.audio.preload_sound_stream_block(self.id(), data);
|
2019-07-25 07:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn preload_sound_stream_head(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
2019-08-16 00:00:48 +00:00
|
|
|
static_data: &mut MovieClipStatic,
|
2019-07-25 07:58:34 +00:00
|
|
|
_version: u8,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let audio_stream_info = reader.read_sound_stream_head()?;
|
|
|
|
context
|
|
|
|
.audio
|
2019-08-16 00:00:48 +00:00
|
|
|
.preload_sound_stream_head(self.id(), &audio_stream_info);
|
|
|
|
static_data.audio_stream_info = Some(audio_stream_info);
|
2019-07-25 07:58:34 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
#[inline]
|
|
|
|
fn define_bits(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
tag_len: usize,
|
|
|
|
) -> DecodeResult {
|
|
|
|
use std::io::Read;
|
|
|
|
let id = reader.read_u16()?;
|
|
|
|
let data_len = tag_len - 2;
|
|
|
|
let mut jpeg_data = Vec::with_capacity(data_len);
|
|
|
|
reader
|
|
|
|
.get_mut()
|
|
|
|
.take(data_len as u64)
|
|
|
|
.read_to_end(&mut jpeg_data)?;
|
|
|
|
let handle = context.renderer.register_bitmap_jpeg(
|
|
|
|
id,
|
|
|
|
&jpeg_data,
|
|
|
|
context.library.jpeg_tables().unwrap(),
|
|
|
|
);
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(id, Character::Bitmap(handle));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn define_bits_jpeg_2(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
tag_len: usize,
|
|
|
|
) -> DecodeResult {
|
|
|
|
use std::io::Read;
|
|
|
|
let id = reader.read_u16()?;
|
|
|
|
let data_len = tag_len - 2;
|
|
|
|
let mut jpeg_data = Vec::with_capacity(data_len);
|
|
|
|
reader
|
|
|
|
.get_mut()
|
|
|
|
.take(data_len as u64)
|
|
|
|
.read_to_end(&mut jpeg_data)?;
|
|
|
|
let handle = context.renderer.register_bitmap_jpeg_2(id, &jpeg_data);
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(id, Character::Bitmap(handle));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-12 23:44:49 +00:00
|
|
|
#[inline]
|
|
|
|
fn define_bits_jpeg_3(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
tag_len: usize,
|
|
|
|
) -> DecodeResult {
|
|
|
|
use std::io::Read;
|
|
|
|
let id = reader.read_u16()?;
|
|
|
|
let jpeg_len = reader.read_u32()? as usize;
|
|
|
|
let alpha_len = tag_len - 6 - jpeg_len;
|
|
|
|
let mut jpeg_data = Vec::with_capacity(jpeg_len);
|
|
|
|
let mut alpha_data = Vec::with_capacity(alpha_len);
|
|
|
|
reader
|
|
|
|
.get_mut()
|
|
|
|
.take(jpeg_len as u64)
|
|
|
|
.read_to_end(&mut jpeg_data)?;
|
|
|
|
reader
|
|
|
|
.get_mut()
|
|
|
|
.take(alpha_len as u64)
|
|
|
|
.read_to_end(&mut alpha_data)?;
|
|
|
|
let handle = context
|
|
|
|
.renderer
|
|
|
|
.register_bitmap_jpeg_3(id, &jpeg_data, &alpha_data);
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(id, Character::Bitmap(handle));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn define_bits_jpeg_4(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
tag_len: usize,
|
|
|
|
) -> DecodeResult {
|
|
|
|
use std::io::Read;
|
|
|
|
let id = reader.read_u16()?;
|
|
|
|
let jpeg_len = reader.read_u32()? as usize;
|
|
|
|
let _deblocking = reader.read_u16()?;
|
|
|
|
let alpha_len = tag_len - 6 - jpeg_len;
|
|
|
|
let mut jpeg_data = Vec::with_capacity(jpeg_len);
|
|
|
|
let mut alpha_data = Vec::with_capacity(alpha_len);
|
|
|
|
reader
|
|
|
|
.get_mut()
|
|
|
|
.take(jpeg_len as u64)
|
|
|
|
.read_to_end(&mut jpeg_data)?;
|
|
|
|
reader
|
|
|
|
.get_mut()
|
|
|
|
.take(alpha_len as u64)
|
|
|
|
.read_to_end(&mut alpha_data)?;
|
|
|
|
let handle = context
|
|
|
|
.renderer
|
|
|
|
.register_bitmap_jpeg_3(id, &jpeg_data, &alpha_data);
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(id, Character::Bitmap(handle));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
#[inline]
|
|
|
|
fn define_button_1(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let swf_button = reader.read_define_button_1()?;
|
|
|
|
let button =
|
|
|
|
crate::button::Button::from_swf_tag(&swf_button, &context.library, context.gc_context);
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(swf_button.id, Character::Button(Box::new(button)));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn define_button_2(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let swf_button = reader.read_define_button_2()?;
|
|
|
|
let button =
|
|
|
|
crate::button::Button::from_swf_tag(&swf_button, &context.library, context.gc_context);
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(swf_button.id, Character::Button(Box::new(button)));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn define_font_1(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let font = reader.read_define_font_1()?;
|
|
|
|
let glyphs = font
|
|
|
|
.glyphs
|
|
|
|
.into_iter()
|
|
|
|
.map(|g| swf::Glyph {
|
|
|
|
shape_records: g,
|
|
|
|
code: 0,
|
|
|
|
advance: None,
|
|
|
|
bounds: None,
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let font = swf::Font {
|
|
|
|
id: font.id,
|
|
|
|
version: 0,
|
|
|
|
name: "".to_string(),
|
|
|
|
glyphs,
|
|
|
|
language: swf::Language::Unknown,
|
|
|
|
layout: None,
|
|
|
|
is_small_text: false,
|
|
|
|
is_shift_jis: false,
|
|
|
|
is_ansi: false,
|
|
|
|
is_bold: false,
|
|
|
|
is_italic: false,
|
|
|
|
};
|
|
|
|
let font_object = Font::from_swf_tag(context.renderer, &font).unwrap();
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(font.id, Character::Font(Box::new(font_object)));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn define_font_2(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let font = reader.read_define_font_2(2)?;
|
|
|
|
let font_object = Font::from_swf_tag(context.renderer, &font).unwrap();
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(font.id, Character::Font(Box::new(font_object)));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn define_font_3(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let font = reader.read_define_font_2(3)?;
|
|
|
|
let font_object = Font::from_swf_tag(context.renderer, &font).unwrap();
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(font.id, Character::Font(Box::new(font_object)));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn define_sound(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
2019-07-25 07:58:34 +00:00
|
|
|
tag_len: usize,
|
2019-07-19 08:32:41 +00:00
|
|
|
) -> DecodeResult {
|
|
|
|
// TODO(Herschel): Can we use a slice of the sound data instead of copying the data?
|
2019-07-25 07:58:34 +00:00
|
|
|
use std::io::Read;
|
|
|
|
let mut reader =
|
|
|
|
swf::read::Reader::new(reader.get_mut().take(tag_len as u64), context.swf_version);
|
2019-07-19 08:32:41 +00:00
|
|
|
let sound = reader.read_define_sound()?;
|
|
|
|
let handle = context.audio.register_sound(&sound).unwrap();
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(sound.id, Character::Sound(handle));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn define_sprite(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
tag_len: usize,
|
2019-09-05 05:44:35 +00:00
|
|
|
morph_shapes: &mut fnv::FnvHashMap<CharacterId, MorphShapeStatic>,
|
2019-07-19 08:32:41 +00:00
|
|
|
) -> DecodeResult {
|
|
|
|
let id = reader.read_character_id()?;
|
|
|
|
let num_frames = reader.read_u16()?;
|
2019-08-19 14:53:12 +00:00
|
|
|
let mut movie_clip = MovieClip::new_with_data(
|
|
|
|
context.gc_context,
|
|
|
|
id,
|
|
|
|
reader.get_ref().position(),
|
|
|
|
tag_len - 4,
|
|
|
|
num_frames,
|
|
|
|
);
|
2019-07-19 08:32:41 +00:00
|
|
|
|
2019-09-05 05:44:35 +00:00
|
|
|
movie_clip.preload(context, morph_shapes);
|
2019-07-19 08:32:41 +00:00
|
|
|
|
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(id, Character::MovieClip(Box::new(movie_clip)));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn define_text(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let text = reader.read_define_text()?;
|
2019-09-05 05:44:35 +00:00
|
|
|
let text_object = Text::from_swf_tag(context, &text);
|
2019-07-19 08:32:41 +00:00
|
|
|
context
|
|
|
|
.library
|
|
|
|
.register_character(text.id, Character::Text(Box::new(text_object)));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-16 00:00:48 +00:00
|
|
|
#[inline]
|
|
|
|
fn frame_label(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
tag_len: usize,
|
|
|
|
cur_frame: FrameNumber,
|
|
|
|
static_data: &mut MovieClipStatic,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let frame_label = reader.read_frame_label(tag_len)?;
|
2019-08-19 14:53:12 +00:00
|
|
|
if static_data
|
|
|
|
.frame_labels
|
|
|
|
.insert(frame_label.label, cur_frame)
|
|
|
|
.is_some()
|
|
|
|
{
|
2019-08-16 00:00:48 +00:00
|
|
|
log::warn!("Movie clip {}: Duplicated frame label", self.id());
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
#[inline]
|
|
|
|
fn jpeg_tables(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
tag_len: usize,
|
|
|
|
) -> DecodeResult {
|
|
|
|
use std::io::Read;
|
|
|
|
// TODO(Herschel): Can we use a slice instead of copying?
|
|
|
|
let mut jpeg_data = Vec::with_capacity(tag_len);
|
|
|
|
reader
|
|
|
|
.get_mut()
|
|
|
|
.take(tag_len as u64)
|
|
|
|
.read_to_end(&mut jpeg_data)?;
|
|
|
|
context.library.set_jpeg_tables(jpeg_data);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn preload_remove_object(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
ids: &mut fnv::FnvHashMap<Depth, CharacterId>,
|
|
|
|
version: u8,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let remove_object = if version == 1 {
|
|
|
|
reader.read_remove_object_1()
|
|
|
|
} else {
|
|
|
|
reader.read_remove_object_2()
|
|
|
|
}?;
|
|
|
|
ids.remove(&remove_object.depth);
|
|
|
|
Ok(())
|
|
|
|
}
|
2019-08-16 00:00:48 +00:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn preload_show_frame(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
_reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
cur_frame: &mut FrameNumber,
|
|
|
|
) -> DecodeResult {
|
|
|
|
*cur_frame += 1;
|
|
|
|
Ok(())
|
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Control tags
|
|
|
|
impl<'gc, 'a> MovieClip<'gc> {
|
|
|
|
#[inline]
|
|
|
|
fn do_action(
|
|
|
|
&mut self,
|
2019-07-25 07:58:34 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
tag_len: usize,
|
2019-07-19 08:32:41 +00:00
|
|
|
) -> DecodeResult {
|
2019-07-25 07:58:34 +00:00
|
|
|
// Queue the actions.
|
2019-08-14 19:39:26 +00:00
|
|
|
// TODO: The reader is actually reading the tag slice at this point (tag_stream.take()),
|
|
|
|
// so make sure to get the proper offsets. This feels kind of bad.
|
2019-08-16 00:00:48 +00:00
|
|
|
let start = (self.tag_stream_start() + reader.get_ref().position()) as usize;
|
2019-08-14 19:39:26 +00:00
|
|
|
let end = start + tag_len;
|
2019-07-25 07:58:34 +00:00
|
|
|
let slice = crate::tag_utils::SwfSlice {
|
|
|
|
data: std::sync::Arc::clone(context.swf_data),
|
2019-08-14 19:39:26 +00:00
|
|
|
start,
|
|
|
|
end,
|
2019-07-25 07:58:34 +00:00
|
|
|
};
|
2019-08-14 19:39:26 +00:00
|
|
|
context.actions.push((context.active_clip, slice));
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn place_object(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
2019-08-19 14:53:12 +00:00
|
|
|
tag_len: usize,
|
2019-07-19 08:32:41 +00:00
|
|
|
version: u8,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let place_object = if version == 1 {
|
2019-08-16 17:03:16 +00:00
|
|
|
reader.read_place_object(tag_len)
|
2019-07-19 08:32:41 +00:00
|
|
|
} else {
|
|
|
|
reader.read_place_object_2_or_3(version)
|
|
|
|
}?;
|
2019-04-25 17:52:22 +00:00
|
|
|
use swf::PlaceObjectAction;
|
2019-04-28 06:08:59 +00:00
|
|
|
let character = match place_object.action {
|
2019-04-25 17:52:22 +00:00
|
|
|
PlaceObjectAction::Place(id) => {
|
|
|
|
// TODO(Herschel): Behavior when character doesn't exist/isn't a DisplayObject?
|
2019-05-24 17:25:03 +00:00
|
|
|
let character = if let Ok(character) = context
|
|
|
|
.library
|
|
|
|
.instantiate_display_object(id, context.gc_context)
|
|
|
|
{
|
|
|
|
character
|
|
|
|
} else {
|
2019-07-19 08:32:41 +00:00
|
|
|
return Ok(());
|
2019-05-24 17:25:03 +00:00
|
|
|
};
|
2019-04-25 17:52:22 +00:00
|
|
|
|
|
|
|
// TODO(Herschel): Behavior when depth is occupied? (I think it replaces)
|
2019-08-14 19:39:26 +00:00
|
|
|
character
|
|
|
|
.write(context.gc_context)
|
|
|
|
.set_parent(Some(context.active_clip));
|
2019-05-24 17:25:03 +00:00
|
|
|
self.children.insert(place_object.depth, character);
|
|
|
|
self.children.get_mut(&place_object.depth).unwrap()
|
2019-04-26 03:27:44 +00:00
|
|
|
}
|
|
|
|
PlaceObjectAction::Modify => {
|
2019-05-24 17:25:03 +00:00
|
|
|
if let Some(child) = self.children.get_mut(&place_object.depth) {
|
|
|
|
child
|
2019-04-26 03:27:44 +00:00
|
|
|
} else {
|
2019-07-19 08:32:41 +00:00
|
|
|
return Ok(());
|
2019-04-26 03:27:44 +00:00
|
|
|
}
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
|
|
|
PlaceObjectAction::Replace(id) => {
|
2019-05-24 17:25:03 +00:00
|
|
|
let character = if let Ok(character) = context
|
|
|
|
.library
|
|
|
|
.instantiate_display_object(id, context.gc_context)
|
2019-05-01 16:55:54 +00:00
|
|
|
{
|
|
|
|
character
|
2019-05-24 17:25:03 +00:00
|
|
|
} else {
|
2019-07-19 08:32:41 +00:00
|
|
|
return Ok(());
|
2019-05-24 17:25:03 +00:00
|
|
|
};
|
|
|
|
|
2019-08-14 19:39:26 +00:00
|
|
|
character
|
|
|
|
.write(context.gc_context)
|
|
|
|
.set_parent(Some(context.active_clip));
|
2019-05-24 17:25:03 +00:00
|
|
|
let prev_character = self.children.insert(place_object.depth, character);
|
|
|
|
let character = self.children.get_mut(&place_object.depth).unwrap();
|
|
|
|
if let Some(prev_character) = prev_character {
|
2019-05-01 16:55:54 +00:00
|
|
|
character
|
2019-05-24 17:25:03 +00:00
|
|
|
.write(context.gc_context)
|
2019-08-14 18:59:07 +00:00
|
|
|
.set_matrix(prev_character.read().matrix());
|
2019-05-24 17:25:03 +00:00
|
|
|
character
|
|
|
|
.write(context.gc_context)
|
2019-08-14 18:59:07 +00:00
|
|
|
.set_color_transform(prev_character.read().color_transform());
|
2019-09-08 02:33:06 +00:00
|
|
|
character
|
|
|
|
.write(context.gc_context)
|
|
|
|
.set_clip_depth(prev_character.read().clip_depth());
|
2019-05-01 16:55:54 +00:00
|
|
|
}
|
2019-04-26 03:27:44 +00:00
|
|
|
character
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
2019-04-26 03:27:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(matrix) = &place_object.matrix {
|
|
|
|
let m = matrix.clone();
|
2019-05-24 17:25:03 +00:00
|
|
|
character
|
|
|
|
.write(context.gc_context)
|
|
|
|
.set_matrix(&Matrix::from(m));
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
2019-05-01 16:55:54 +00:00
|
|
|
|
|
|
|
if let Some(color_transform) = &place_object.color_transform {
|
2019-05-24 17:25:03 +00:00
|
|
|
character
|
|
|
|
.write(context.gc_context)
|
|
|
|
.set_color_transform(&ColorTransform::from(color_transform.clone()));
|
2019-05-01 16:55:54 +00:00
|
|
|
}
|
2019-05-06 18:15:52 +00:00
|
|
|
|
|
|
|
if let Some(name) = &place_object.name {
|
2019-05-24 17:25:03 +00:00
|
|
|
character.write(context.gc_context).set_name(name);
|
2019-05-06 18:15:52 +00:00
|
|
|
}
|
2019-05-12 16:55:48 +00:00
|
|
|
|
|
|
|
if let Some(ratio) = &place_object.ratio {
|
2019-05-24 17:25:03 +00:00
|
|
|
if let Some(morph_shape) = character.write(context.gc_context).as_morph_shape_mut() {
|
2019-05-12 16:55:48 +00:00
|
|
|
morph_shape.set_ratio(*ratio);
|
|
|
|
}
|
|
|
|
}
|
2019-05-12 17:48:00 +00:00
|
|
|
|
|
|
|
if let Some(clip_depth) = &place_object.clip_depth {
|
2019-05-24 17:25:03 +00:00
|
|
|
character
|
|
|
|
.write(context.gc_context)
|
|
|
|
.set_clip_depth(*clip_depth);
|
2019-05-12 17:48:00 +00:00
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
2019-04-30 08:53:21 +00:00
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
#[inline]
|
|
|
|
fn remove_object(
|
2019-05-24 17:25:03 +00:00
|
|
|
&mut self,
|
2019-08-19 14:53:12 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-07-19 08:32:41 +00:00
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
version: u8,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let remove_object = if version == 1 {
|
|
|
|
reader.read_remove_object_1()
|
2019-05-02 00:46:49 +00:00
|
|
|
} else {
|
2019-07-19 08:32:41 +00:00
|
|
|
reader.read_remove_object_2()
|
|
|
|
}?;
|
2019-08-19 14:53:12 +00:00
|
|
|
if let Some(child) = self.children.remove(&remove_object.depth) {
|
|
|
|
child.write(context.gc_context).set_parent(None);
|
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
2019-05-02 00:46:49 +00:00
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
#[inline]
|
|
|
|
fn set_background_color(
|
2019-05-02 00:46:49 +00:00
|
|
|
&mut self,
|
2019-07-19 08:32:41 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
) -> DecodeResult {
|
|
|
|
*context.background_color = reader.read_rgb()?;
|
|
|
|
Ok(())
|
2019-05-09 19:43:26 +00:00
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
#[inline]
|
|
|
|
fn sound_stream_block(
|
2019-05-09 19:43:26 +00:00
|
|
|
&mut self,
|
2019-07-25 07:58:34 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-07-19 08:32:41 +00:00
|
|
|
_reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
) -> DecodeResult {
|
2019-08-19 14:53:12 +00:00
|
|
|
if let (Some(stream_info), None) = (&self.static_data.audio_stream_info, &self.audio_stream)
|
|
|
|
{
|
2019-07-25 07:58:34 +00:00
|
|
|
let slice = crate::tag_utils::SwfSlice {
|
|
|
|
data: std::sync::Arc::clone(context.swf_data),
|
2019-08-16 00:00:48 +00:00
|
|
|
start: self.tag_stream_start() as usize,
|
|
|
|
end: self.tag_stream_start() as usize + self.tag_stream_len(),
|
2019-07-25 07:58:34 +00:00
|
|
|
};
|
2019-08-16 00:00:48 +00:00
|
|
|
let audio_stream = context.audio.start_stream(self.id(), slice, stream_info);
|
2019-07-25 07:58:34 +00:00
|
|
|
self.audio_stream = Some(audio_stream);
|
|
|
|
}
|
2019-05-02 00:46:49 +00:00
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
2019-05-24 17:25:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2019-07-19 08:32:41 +00:00
|
|
|
fn start_sound_1(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
reader: &mut SwfStream<&'a [u8]>,
|
|
|
|
) -> DecodeResult {
|
|
|
|
let start_sound = reader.read_start_sound_1()?;
|
|
|
|
if let Some(handle) = context.library.get_sound(start_sound.id) {
|
|
|
|
context.audio.play_sound(handle);
|
2019-05-24 17:25:03 +00:00
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
2019-05-07 06:31:34 +00:00
|
|
|
}
|
2019-04-25 17:52:22 +00:00
|
|
|
}
|
2019-08-16 00:00:48 +00:00
|
|
|
|
|
|
|
/// Static data shared between all instances of a movie clip.
|
|
|
|
#[allow(dead_code)]
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct MovieClipStatic {
|
|
|
|
id: CharacterId,
|
|
|
|
tag_stream_start: u64,
|
|
|
|
tag_stream_len: usize,
|
|
|
|
frame_labels: HashMap<String, FrameNumber>,
|
|
|
|
audio_stream_info: Option<swf::SoundStreamHead>,
|
|
|
|
total_frames: FrameNumber,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for MovieClipStatic {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
id: 0,
|
|
|
|
tag_stream_start: 0,
|
|
|
|
tag_stream_len: 0,
|
|
|
|
total_frames: 1,
|
|
|
|
frame_labels: HashMap::new(),
|
|
|
|
audio_stream_info: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl<'gc> gc_arena::Collect for MovieClipStatic {
|
|
|
|
#[inline]
|
|
|
|
fn needs_trace() -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|