avm2: Run DoAbc/DoAbc2/SymbolClass as part of their frame

We previously ran these tags during preloading - however,
they are actually run as part of frame execution. This is observable
by ActionScript - a SWF can load in a class from a stop()'d MoveClip,
and then advance the clip to a frame with a SymbolClass referencing
the loaded class.
This commit is contained in:
Aaron Hill 2023-10-01 08:05:22 -04:00
parent 6394b29962
commit ddefac322a
8 changed files with 155 additions and 9 deletions

View File

@ -46,7 +46,7 @@ use std::cmp::max;
use std::collections::HashMap;
use std::sync::Arc;
use swf::extensions::ReadSwfExt;
use swf::{ClipEventFlag, FontFlag, FrameLabelData, SwfStr};
use swf::{ClipEventFlag, FontFlag, FrameLabelData, SwfStr, TagCode};
use super::interactive::Avm2MousePick;
@ -452,8 +452,6 @@ impl<'gc> MovieClip<'gc> {
context: &mut UpdateContext<'_, 'gc>,
chunk_limit: &mut ExecutionLimit,
) -> bool {
use swf::TagCode;
{
let read = self.0.read();
if read.static_data.preload_progress.read().next_preload_chunk
@ -641,9 +639,6 @@ impl<'gc> MovieClip<'gc> {
.write(context.gc_context)
.define_text(context, reader, 2),
TagCode::DoInitAction => self.do_init_action(context, reader, tag_len),
TagCode::DoAbc => self.do_abc(context, reader),
TagCode::DoAbc2 => self.do_abc_2(context, reader),
TagCode::SymbolClass => self.symbol_class(context, reader),
TagCode::DefineSceneAndFrameLabelData => {
self.scene_and_frame_labels(reader, &mut static_data)
}
@ -1429,7 +1424,7 @@ impl<'gc> MovieClip<'gc> {
_context: &mut UpdateContext<'_, 'gc>,
frame: FrameNumber,
) -> impl DoubleEndedIterator<Item = SwfSlice> {
use swf::{read::Reader, TagCode};
use swf::read::Reader;
let mut actions: SmallVec<[SwfSlice; 2]> = SmallVec::new();
@ -1506,7 +1501,6 @@ impl<'gc> MovieClip<'gc> {
let mut reader = data.read_from(mc.tag_stream_pos);
drop(mc);
use swf::TagCode;
let tag_callback = |reader: &mut SwfStream<'_>, tag_code, tag_len| {
match tag_code {
TagCode::DoAction => self.do_action(context, reader, tag_len),
@ -1549,6 +1543,9 @@ impl<'gc> MovieClip<'gc> {
TagCode::SetBackgroundColor => self.set_background_color(context, reader),
TagCode::StartSound if run_sounds => self.start_sound_1(context, reader),
TagCode::SoundStreamBlock if run_sounds => self.sound_stream_block(context, reader),
TagCode::DoAbc | TagCode::DoAbc2 | TagCode::SymbolClass => {
self.handle_bytecode_tag(tag_code, reader, context)
}
TagCode::ShowFrame => return Ok(ControlFlow::Exit),
_ => Ok(()),
}?;
@ -1824,7 +1821,6 @@ impl<'gc> MovieClip<'gc> {
frame_pos = reader.get_ref().as_ptr() as u64 - tag_stream_start;
let tag_callback = |reader: &mut _, tag_code, _tag_len| {
use swf::TagCode;
match tag_code {
TagCode::PlaceObject => {
index += 1;
@ -1864,6 +1860,9 @@ impl<'gc> MovieClip<'gc> {
from_frame,
&mut removed_frame_scripts,
),
TagCode::DoAbc | TagCode::DoAbc2 | TagCode::SymbolClass => {
self.handle_bytecode_tag(tag_code, reader, context)
}
TagCode::ShowFrame => return Ok(ControlFlow::Exit),
_ => Ok(()),
}?;
@ -4150,6 +4149,33 @@ impl<'gc, 'a> MovieClip<'gc> {
Ok(())
}
/// Handles a DoAbc, DoAbc2, or SymbolClass tag
fn handle_bytecode_tag(
self,
tag_code: TagCode,
reader: &mut SwfStream<'a>,
context: &mut UpdateContext<'_, 'gc>,
) -> Result<(), Error> {
let mc = self.0.read();
let tag_stream_start = mc.static_data.swf.as_ref().as_ptr() as u64;
let tag_start = reader.get_ref().as_ptr() as u64 - tag_stream_start;
let processed_pos = self.0.read().static_data.processed_bytecode_tags_pos;
if *processed_pos.read() < tag_start as i64 {
*processed_pos.write(context.gc_context) = tag_start as i64;
drop(mc);
match tag_code {
TagCode::DoAbc => self.do_abc(context, reader),
TagCode::DoAbc2 => self.do_abc_2(context, reader),
TagCode::SymbolClass => self.symbol_class(context, reader),
_ => unreachable!(),
}
} else {
Ok(())
}
}
fn queue_place_object(
self,
context: &mut UpdateContext<'_, 'gc>,
@ -4438,6 +4464,12 @@ struct MovieClipStatic<'gc> {
/// Preload progress for the given clip's tag stream.
preload_progress: GcCell<'gc, PreloadProgress>,
/// Holds the tag offset for the furthest DoAbc/DoAbc2/SymbolClass tags that we've
/// already run. These tags are run as part of normal frame processing - this
/// is observable by ActionScript, which might load a class in a stop()'d MovieClip,
/// and then advance to a frame containing a SymbolClass that references the loaded class.
processed_bytecode_tags_pos: GcCell<'gc, i64>,
}
impl<'gc> MovieClipStatic<'gc> {
@ -4469,6 +4501,7 @@ impl<'gc> MovieClipStatic<'gc> {
exported_name: GcCell::new(gc_context, None),
loader_info,
preload_progress: GcCell::new(gc_context, Default::default()),
processed_bytecode_tags_pos: GcCell::new(gc_context, -1),
}
}
}

View File

@ -0,0 +1,22 @@
package {
import flash.display.MovieClip;
public class FourthFrameChild extends MovieClip {
public static var DUMMY: String = myFunc();
public static function myFunc():String {
trace("In FourthFrameChild class initializer");
return "FOO";
}
public function SecondFrameChild() {
trace("Constructed FourthFrameChild")
}
}
}
trace("In FourthFrameChild script initializer");

View File

@ -0,0 +1,40 @@
package {
import flash.display.MovieClip;
import flash.events.Event;
import flash.utils.getDefinitionByName;
public class Main extends MovieClip {
public function Main() {
trace("In constructor");
root.loaderInfo.addEventListener("open", function(e) {
trace("ERROR: Called open event!");
});
root.loaderInfo.addEventListener("init", function(e) {
trace("Called init event!");
});
root.loaderInfo.addEventListener("complete", function(e) {
trace("Called complete event!");
});
this.addEventListener(Event.ENTER_FRAME, function(e) {
trace("Called enterFrame");
try {
trace("SecondFrameChild: " + getDefinitionByName("SecondFrameChild"));
} catch (e) {
trace("Caught error in Main enterFrame: " + e);
}
try {
trace("FourthFrameChild: " + getDefinitionByName("FourthFrameChild"));
} catch (e) {
trace("Caught error in Main enterFrame: " + e);
}
});
this.addEventListener(Event.FRAME_CONSTRUCTED, function(e) {
trace("Called frameConstructed");
})
trace("Finished constructor");
}
}
}

View File

@ -0,0 +1,22 @@
package {
import flash.display.MovieClip;
public class SecondFrameChild extends MovieClip {
public static var DUMMY: String = myFunc();
public static function myFunc():String {
trace("In SecondFrameChild class initializer");
return "FOO";
}
public function SecondFrameChild() {
trace("Constructed SecondFrameChild")
}
}
}
trace("In SecondFrameChild script initializer");

View File

@ -0,0 +1,28 @@
In constructor
Finished constructor
Called frameConstructed
Main framescript 1
Called init event!
Called complete event!
In SecondFrameChild class initializer
In SecondFrameChild script initializer
Called enterFrame
SecondFrameChild: [class SecondFrameChild]
In FourthFrameChild class initializer
In FourthFrameChild script initializer
FourthFrameChild: [class FourthFrameChild]
Constructed SecondFrameChild
Called frameConstructed
Main framescript 2
Called enterFrame
SecondFrameChild: [class SecondFrameChild]
FourthFrameChild: [class FourthFrameChild]
Called frameConstructed
Main framescript 3 - running gotoAndPlay(5)
Called frameConstructed
Main framescript 5
Called enterFrame
SecondFrameChild: [class SecondFrameChild]
FourthFrameChild: [class FourthFrameChild]
Called frameConstructed
Main framescript 1

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_ticks = 4