Implement `DoInitAction`.

This pushes an extra `undefined` onto the stack to fix underflow in AS2 interface declarations.

It is currently unknown if this is a miscompilation or if some other value is supposed to be there.

# Conflicts:
#	core/src/avm1.rs
#	core/src/avm1/object.rs
This commit is contained in:
David Wendt 2019-10-29 00:07:47 -04:00 committed by Mike Welsh
parent 33c0bbd0ce
commit 6a81b5327d
5 changed files with 80 additions and 5 deletions

View File

@ -137,6 +137,35 @@ impl<'gc> Avm1<'gc> {
));
}
/// Add a stack frame that executes code in initializer scope
pub fn insert_stack_frame_for_init_action(
&mut self,
swf_version: u8,
code: SwfSlice,
action_context: &mut UpdateContext<'_, 'gc, '_>,
) {
let global_scope = GcCell::allocate(
action_context.gc_context,
Scope::from_global_object(self.globals),
);
let clip_obj = action_context
.active_clip
.read()
.object()
.as_object()
.unwrap()
.to_owned();
let child_scope = GcCell::allocate(
action_context.gc_context,
Scope::new(global_scope, scope::ScopeClass::Target, clip_obj),
);
self.push(Value::Undefined);
self.stack_frames.push(GcCell::allocate(
action_context.gc_context,
Activation::from_action(swf_version, code, child_scope, clip_obj, None),
));
}
/// Add a stack frame for any arbitrary code.
pub fn insert_stack_frame(
&mut self,

View File

@ -90,6 +90,9 @@ pub struct QueuedActions<'gc> {
/// The ActionScript bytecode.
pub actions: SwfSlice,
/// If this queued action is an init action
pub is_init: bool,
}
/// Action and gotos need to be queued up to execute at the end of the frame.
@ -110,8 +113,12 @@ impl<'gc> ActionQueue<'gc> {
/// Queues ActionScript to run for the given movie clip.
/// `actions` is the slice of ActionScript bytecode to run.
/// The actions will be skipped if the clip is removed before the actions run.
pub fn queue_actions(&mut self, clip: DisplayNode<'gc>, actions: SwfSlice) {
self.queue.push_back(QueuedActions { clip, actions })
pub fn queue_actions(&mut self, clip: DisplayNode<'gc>, actions: SwfSlice, is_init: bool) {
self.queue.push_back(QueuedActions {
clip,
actions,
is_init,
})
}
/// Pops the next actions off of the queue.

View File

@ -139,7 +139,7 @@ impl<'gc> Button<'gc> {
// Note that AVM1 buttons run actions relative to their parent, not themselves.
context
.action_queue
.queue_actions(parent, action.action_data.clone());
.queue_actions(parent, action.action_data.clone(), false);
}
}
}

View File

@ -665,6 +665,7 @@ impl<'gc, 'a> MovieClip<'gc> {
TagCode::DefineSprite => self.define_sprite(context, reader, tag_len, morph_shapes),
TagCode::DefineText => self.define_text(context, reader, 1),
TagCode::DefineText2 => self.define_text(context, reader, 2),
TagCode::DoInitAction => self.do_init_action(context, reader, tag_len),
TagCode::FrameLabel => {
self.frame_label(context, reader, tag_len, cur_frame, &mut static_data)
}
@ -1236,7 +1237,36 @@ impl<'gc, 'a> MovieClip<'gc> {
};
context
.action_queue
.queue_actions(context.active_clip, slice);
.queue_actions(context.active_clip, slice, false);
Ok(())
}
#[inline]
fn do_init_action(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
reader: &mut SwfStream<&'a [u8]>,
tag_len: usize,
) -> DecodeResult {
// Queue the init actions.
// TODO: Init actions are supposed to be executed once, and it gives a
// sprite ID... how does that work?
let sprite_id = reader.read_u16()?;
log::info!("Init Action sprite ID {}", sprite_id);
// 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.
let start = (self.tag_stream_start() + reader.get_ref().position()) as usize;
let end = start + tag_len;
let slice = crate::tag_utils::SwfSlice {
data: std::sync::Arc::clone(context.swf_data),
start,
end,
};
context
.action_queue
.queue_actions(context.active_clip, slice, true);
Ok(())
}

View File

@ -622,7 +622,16 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
context.start_clip = actions.clip;
context.active_clip = actions.clip;
context.target_clip = Some(actions.clip);
if actions.is_init {
avm.insert_stack_frame_for_init_action(
context.swf_version,
actions.actions,
context,
);
} else {
avm.insert_stack_frame_for_action(context.swf_version, actions.actions, context);
}
let _ = avm.run_stack_till_empty(context);
}
}