From ddefac322a03a9cc54268c6674bab51b84021071 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 1 Oct 2023 08:05:22 -0400 Subject: [PATCH] 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. --- core/src/display_object/movie_clip.rs | 51 ++++++++++++++---- .../delayed_symbolclass/FourthFrameChild.as | 22 ++++++++ .../swfs/avm2/delayed_symbolclass/Main.as | 40 ++++++++++++++ .../delayed_symbolclass/SecondFrameChild.as | 22 ++++++++ .../swfs/avm2/delayed_symbolclass/output.txt | 28 ++++++++++ .../swfs/avm2/delayed_symbolclass/test.fla | Bin 0 -> 5690 bytes .../swfs/avm2/delayed_symbolclass/test.swf | Bin 0 -> 1581 bytes .../swfs/avm2/delayed_symbolclass/test.toml | 1 + 8 files changed, 155 insertions(+), 9 deletions(-) create mode 100755 tests/tests/swfs/avm2/delayed_symbolclass/FourthFrameChild.as create mode 100755 tests/tests/swfs/avm2/delayed_symbolclass/Main.as create mode 100755 tests/tests/swfs/avm2/delayed_symbolclass/SecondFrameChild.as create mode 100644 tests/tests/swfs/avm2/delayed_symbolclass/output.txt create mode 100755 tests/tests/swfs/avm2/delayed_symbolclass/test.fla create mode 100755 tests/tests/swfs/avm2/delayed_symbolclass/test.swf create mode 100644 tests/tests/swfs/avm2/delayed_symbolclass/test.toml diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index e3f316f9e11e..507e5fbc2186 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -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 { - 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), } } } diff --git a/tests/tests/swfs/avm2/delayed_symbolclass/FourthFrameChild.as b/tests/tests/swfs/avm2/delayed_symbolclass/FourthFrameChild.as new file mode 100755 index 000000000000..43e94be65801 --- /dev/null +++ b/tests/tests/swfs/avm2/delayed_symbolclass/FourthFrameChild.as @@ -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"); \ No newline at end of file diff --git a/tests/tests/swfs/avm2/delayed_symbolclass/Main.as b/tests/tests/swfs/avm2/delayed_symbolclass/Main.as new file mode 100755 index 000000000000..7af55bfdd205 --- /dev/null +++ b/tests/tests/swfs/avm2/delayed_symbolclass/Main.as @@ -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"); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/delayed_symbolclass/SecondFrameChild.as b/tests/tests/swfs/avm2/delayed_symbolclass/SecondFrameChild.as new file mode 100755 index 000000000000..d06abe01ceef --- /dev/null +++ b/tests/tests/swfs/avm2/delayed_symbolclass/SecondFrameChild.as @@ -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"); \ No newline at end of file diff --git a/tests/tests/swfs/avm2/delayed_symbolclass/output.txt b/tests/tests/swfs/avm2/delayed_symbolclass/output.txt new file mode 100644 index 000000000000..f2c1c2ab3de3 --- /dev/null +++ b/tests/tests/swfs/avm2/delayed_symbolclass/output.txt @@ -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 diff --git a/tests/tests/swfs/avm2/delayed_symbolclass/test.fla b/tests/tests/swfs/avm2/delayed_symbolclass/test.fla new file mode 100755 index 0000000000000000000000000000000000000000..9428c18d42ba9cd805fbf3ea80432ca2db0ec4d5 GIT binary patch literal 5690 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 literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/delayed_symbolclass/test.swf b/tests/tests/swfs/avm2/delayed_symbolclass/test.swf new file mode 100755 index 0000000000000000000000000000000000000000..859387f825c4cccdc72ae9486171e351ed42c66e GIT binary patch literal 1581 zcmV+|2GaRMS5qs33jhFkoSjuoY!gQmp4lId*K5bWu@gwb2AmXXqCR% zwH;$q4y^2*neTgV-ZyXF%zljcb%eYR5bEL4tlxzfl(*RK>moX|95{BBG}54%OM#b`OJXo?g==wV25Q2-tv8mG-wk+>J`Jx zCyh)=N#qN~+(c<7S<$kC9r&`6)wSFK&}q>Q2#cA5HbGya)l(@GYw1t6^ru?-(~<;Zcj3a(+BlC~p-rJHn#AT~HFm!j`YylcVt+wnn>|w3{irndWD2EH1w9 zu$9c;j;|UORV^y3rcyS`^>_IrwY-|yE={iH6bPr}*2_Dk4ZWBJ5#@8|+Qp4$%Dic0 zvdYkcCbg52@B;wyA$XGZj!^7Pb`|3BSZcG!yco7>?yFqhKn-uM%3$8ig&hRVh}^ zzfpqd9zarh=a!aIOK~ZYDHdUw`j(CBn9+1?r=(ZP z(J6o3M4^kdga_KUKQ4)O)+%P9SPpI~=B%PxE7G+~JJ-N$Fp;Tj<{_$K=mrRosMwY% z<5HkKGjSify2o8fon}niYVi{3=t_2+Uu#gs&Y+SbB`TLx$)THntLARXRu2Q0OtkH z2V5U;H9vT<7#Q%VE+C*IfN)?eoUO_*bSNn8k<(pr+AF7ha@sGa19F;$-!0T{KY0f1 z!Z2er>BgTwdji3D3_{qde3@p75yjvOJcNY3;hGD~-lBm;ph%N)1LXngIHkIPayBV1 zP*m=ThFpQ;+`ca{z`>g%zd;g4a8N-{N|;&)Yay^5rM6Vj*jjMRmkij8@i1s5&4-^tec0Fc4O({SmpyWob)K)Z`{nUP|rQD`}L2ZeAq-E>z| z`%w^i3_N0Vt|(|11xECIL$#Btd~@U(sGmdx1`DX>TnpEjD5|QCD6Xm`%2ZWnjg4|u zRY--MK)c7@rt=8th}tuDdd89JWndz=H9R{*?4j2h#%G-NFj!3=QV-7v_UI@b_072K z(Gbk|E}d~0l(}hn_^L8s7xrdqnBolJo&)^i-lYS)i{iZ&p4gkM<7@pD|1Q1d~mAp#t&c`r>et)%xF zl@w}}`TXmoFbydj*wLc8(k$}}L7{%`>5YqK(0(Xp0qT#^sc*mkd+pYz-#z+k7mhMg zAMfK3vYvhabYOTakB!f literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/delayed_symbolclass/test.toml b/tests/tests/swfs/avm2/delayed_symbolclass/test.toml new file mode 100644 index 000000000000..9632177a2c3b --- /dev/null +++ b/tests/tests/swfs/avm2/delayed_symbolclass/test.toml @@ -0,0 +1 @@ +num_ticks = 4 \ No newline at end of file