2019-11-30 18:31:11 +00:00
|
|
|
use crate::avm1::function::{Avm1Function, FunctionObject};
|
2019-09-02 18:45:19 +00:00
|
|
|
use crate::avm1::globals::create_globals;
|
2020-01-23 02:14:23 +00:00
|
|
|
use crate::avm1::return_value::ReturnValue;
|
2019-09-03 01:49:36 +00:00
|
|
|
use crate::backend::navigator::NavigationMethod;
|
2019-10-27 19:46:23 +00:00
|
|
|
use crate::context::UpdateContext;
|
2019-08-26 23:38:37 +00:00
|
|
|
use crate::prelude::*;
|
2019-09-15 21:57:35 +00:00
|
|
|
use gc_arena::{GcCell, MutationContext};
|
2019-10-27 18:58:30 +00:00
|
|
|
use rand::Rng;
|
2019-08-26 23:38:37 +00:00
|
|
|
use std::collections::HashMap;
|
2019-10-06 21:45:14 +00:00
|
|
|
use std::convert::TryInto;
|
2019-08-28 23:29:43 +00:00
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
use swf::avm1::read::Reader;
|
2019-09-29 03:11:03 +00:00
|
|
|
use swf::avm1::types::{Action, Function};
|
2019-09-15 21:57:35 +00:00
|
|
|
|
|
|
|
use crate::tag_utils::SwfSlice;
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2019-11-26 19:30:48 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
#[macro_use]
|
|
|
|
mod test_utils;
|
|
|
|
|
2019-12-18 16:45:20 +00:00
|
|
|
#[macro_use]
|
|
|
|
pub mod listeners;
|
|
|
|
|
2019-10-06 21:45:14 +00:00
|
|
|
mod activation;
|
2019-09-26 18:45:45 +00:00
|
|
|
mod fscommand;
|
2019-12-16 21:58:31 +00:00
|
|
|
pub mod function;
|
2019-10-17 02:31:41 +00:00
|
|
|
pub mod globals;
|
2019-08-28 23:29:43 +00:00
|
|
|
pub mod object;
|
2019-10-23 18:45:01 +00:00
|
|
|
mod property;
|
2019-10-21 22:37:04 +00:00
|
|
|
mod return_value;
|
2019-10-06 21:45:14 +00:00
|
|
|
mod scope;
|
2019-10-25 03:21:35 +00:00
|
|
|
pub mod script_object;
|
2019-12-22 23:32:32 +00:00
|
|
|
mod sound_object;
|
2019-11-03 17:44:26 +00:00
|
|
|
mod stage_object;
|
2019-11-04 23:15:32 +00:00
|
|
|
mod super_object;
|
2019-11-03 18:24:47 +00:00
|
|
|
mod value;
|
2020-01-18 05:32:57 +00:00
|
|
|
mod value_object;
|
2019-12-26 05:09:43 +00:00
|
|
|
pub mod xml_attributes_object;
|
2020-01-01 21:04:38 +00:00
|
|
|
pub mod xml_idmap_object;
|
2019-12-22 03:24:27 +00:00
|
|
|
pub mod xml_object;
|
2019-11-02 22:08:06 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
2019-08-31 12:29:46 +00:00
|
|
|
|
2019-12-18 16:45:20 +00:00
|
|
|
use crate::avm1::listeners::SystemListener;
|
2019-09-17 04:04:38 +00:00
|
|
|
use activation::Activation;
|
2019-10-17 02:31:41 +00:00
|
|
|
pub use globals::SystemPrototypes;
|
2019-12-06 18:24:36 +00:00
|
|
|
pub use object::{Object, ObjectPtr, TObject};
|
2019-10-06 21:45:14 +00:00
|
|
|
use scope::Scope;
|
2019-10-25 03:21:35 +00:00
|
|
|
pub use script_object::ScriptObject;
|
2019-12-22 23:32:32 +00:00
|
|
|
pub use sound_object::SoundObject;
|
2019-11-03 18:24:47 +00:00
|
|
|
pub use stage_object::StageObject;
|
2019-10-06 21:45:14 +00:00
|
|
|
pub use value::Value;
|
2019-08-28 19:08:32 +00:00
|
|
|
|
2019-11-26 20:16:48 +00:00
|
|
|
macro_rules! avm_debug {
|
|
|
|
($($arg:tt)*) => (
|
|
|
|
#[cfg(feature = "avm_debug")]
|
|
|
|
log::debug!($($arg)*)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-09-15 19:25:34 +00:00
|
|
|
pub struct Avm1<'gc> {
|
2019-10-13 22:55:39 +00:00
|
|
|
/// The Flash Player version we're emulating.
|
|
|
|
player_version: u8,
|
|
|
|
|
2019-11-03 22:43:28 +00:00
|
|
|
/// The constant pool to use for new activations from code sources that
|
|
|
|
/// don't close over the constant pool they were defined with.
|
|
|
|
constant_pool: GcCell<'gc, Vec<String>>,
|
2019-09-17 05:02:37 +00:00
|
|
|
|
|
|
|
/// The global object.
|
2019-12-06 18:24:36 +00:00
|
|
|
globals: Object<'gc>,
|
2019-09-17 05:02:37 +00:00
|
|
|
|
2019-10-17 02:31:41 +00:00
|
|
|
/// System builtins that we use internally to construct new objects.
|
|
|
|
prototypes: globals::SystemPrototypes<'gc>,
|
|
|
|
|
2019-12-18 16:45:20 +00:00
|
|
|
/// System event listeners that will respond to native events (Mouse, Key, etc)
|
|
|
|
system_listeners: listeners::SystemListeners<'gc>,
|
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
/// DisplayObject property map.
|
|
|
|
display_properties: GcCell<'gc, stage_object::DisplayPropertyMap<'gc>>,
|
|
|
|
|
2019-09-17 05:02:37 +00:00
|
|
|
/// All activation records for the current execution context.
|
2019-10-10 02:58:53 +00:00
|
|
|
stack_frames: Vec<GcCell<'gc, Activation<'gc>>>,
|
2019-09-17 05:02:37 +00:00
|
|
|
|
|
|
|
/// The operand stack (shared across functions).
|
2019-09-25 22:59:02 +00:00
|
|
|
stack: Vec<Value<'gc>>,
|
|
|
|
|
|
|
|
/// The register slots (also shared across functions).
|
|
|
|
/// `ActionDefineFunction2` defined functions do not use these slots.
|
2019-10-02 21:33:33 +00:00
|
|
|
registers: [Value<'gc>; 4],
|
2019-09-15 19:25:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl<'gc> gc_arena::Collect for Avm1<'gc> {
|
|
|
|
#[inline]
|
|
|
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
2019-08-26 22:53:50 +00:00
|
|
|
self.globals.trace(cc);
|
2019-11-03 22:43:28 +00:00
|
|
|
self.constant_pool.trace(cc);
|
2019-12-18 16:45:20 +00:00
|
|
|
self.system_listeners.trace(cc);
|
2019-10-17 02:31:41 +00:00
|
|
|
self.prototypes.trace(cc);
|
2019-12-04 01:52:00 +00:00
|
|
|
self.display_properties.trace(cc);
|
2019-09-15 19:25:34 +00:00
|
|
|
self.stack_frames.trace(cc);
|
2019-09-17 05:02:37 +00:00
|
|
|
self.stack.trace(cc);
|
2019-10-17 02:31:41 +00:00
|
|
|
|
|
|
|
for register in &self.registers {
|
|
|
|
register.trace(cc);
|
|
|
|
}
|
2019-08-26 22:53:50 +00:00
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Error = Box<dyn std::error::Error>;
|
|
|
|
|
2019-08-26 22:53:50 +00:00
|
|
|
impl<'gc> Avm1<'gc> {
|
2019-10-13 22:55:39 +00:00
|
|
|
pub fn new(gc_context: MutationContext<'gc, '_>, player_version: u8) -> Self {
|
2019-12-18 16:45:20 +00:00
|
|
|
let (prototypes, globals, system_listeners) = create_globals(gc_context);
|
2019-10-17 02:31:41 +00:00
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
Self {
|
2019-10-13 22:55:39 +00:00
|
|
|
player_version,
|
2019-11-03 22:43:28 +00:00
|
|
|
constant_pool: GcCell::allocate(gc_context, vec![]),
|
2019-11-26 19:30:48 +00:00
|
|
|
globals,
|
2019-10-17 02:31:41 +00:00
|
|
|
prototypes,
|
2019-12-18 16:45:20 +00:00
|
|
|
system_listeners,
|
2019-12-04 01:52:00 +00:00
|
|
|
display_properties: stage_object::DisplayPropertyMap::new(gc_context),
|
2019-09-15 19:25:34 +00:00
|
|
|
stack_frames: vec![],
|
2019-09-25 22:59:02 +00:00
|
|
|
stack: vec![],
|
2019-10-06 21:45:14 +00:00
|
|
|
registers: [
|
|
|
|
Value::Undefined,
|
|
|
|
Value::Undefined,
|
|
|
|
Value::Undefined,
|
|
|
|
Value::Undefined,
|
|
|
|
],
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-16 01:21:57 +00:00
|
|
|
|
2019-12-19 23:42:26 +00:00
|
|
|
#[allow(dead_code)]
|
|
|
|
pub fn base_clip(&self) -> DisplayObject<'gc> {
|
|
|
|
self.current_stack_frame().unwrap().read().base_clip()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The current target clip for the executing code.
|
|
|
|
/// This is the movie clip that contains the bytecode.
|
|
|
|
/// Timeline actions like `GotoFrame` use this because
|
|
|
|
/// a goto after an invalid tellTarget has no effect.
|
|
|
|
pub fn target_clip(&self) -> Option<DisplayObject<'gc>> {
|
|
|
|
self.current_stack_frame().unwrap().read().target_clip()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The current target clip of the executing code, or `root` if there is none.
|
|
|
|
/// Actions that affect `root` after an invalid `tellTarget` will use this.
|
|
|
|
pub fn target_clip_or_root(
|
|
|
|
&self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> DisplayObject<'gc> {
|
|
|
|
self.current_stack_frame()
|
|
|
|
.unwrap()
|
|
|
|
.read()
|
|
|
|
.target_clip()
|
|
|
|
.unwrap_or(context.root)
|
|
|
|
}
|
|
|
|
|
2019-09-03 01:49:36 +00:00
|
|
|
/// Convert the current locals pool into a set of form values.
|
2019-09-17 03:37:11 +00:00
|
|
|
///
|
2019-09-03 01:49:36 +00:00
|
|
|
/// This is necessary to support form submission from Flash via a couple of
|
|
|
|
/// legacy methods, such as the `ActionGetURL2` opcode or `getURL` function.
|
2019-10-19 02:53:29 +00:00
|
|
|
///
|
|
|
|
/// WARNING: This does not support user defined virtual properties!
|
2019-10-08 14:34:08 +00:00
|
|
|
pub fn locals_into_form_values(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-08 14:34:08 +00:00
|
|
|
) -> HashMap<String, String> {
|
2019-09-03 01:49:36 +00:00
|
|
|
let mut form_values = HashMap::new();
|
2019-12-06 18:24:36 +00:00
|
|
|
let stack_frame = self.current_stack_frame().unwrap();
|
|
|
|
let stack_frame = stack_frame.read();
|
|
|
|
let scope = stack_frame.scope();
|
|
|
|
let locals = scope.locals();
|
|
|
|
let keys = locals.get_keys();
|
2019-10-08 14:34:08 +00:00
|
|
|
|
2019-11-02 21:31:03 +00:00
|
|
|
for k in keys {
|
2019-12-10 09:33:22 +00:00
|
|
|
let v = locals.get(&k, self, context);
|
2019-10-21 22:37:04 +00:00
|
|
|
|
2019-10-26 03:21:14 +00:00
|
|
|
//TODO: What happens if an error occurs inside a virtual property?
|
|
|
|
form_values.insert(
|
|
|
|
k,
|
|
|
|
v.ok()
|
2019-10-31 00:25:52 +00:00
|
|
|
.unwrap_or_else(|| Value::Undefined.into())
|
2019-10-26 03:21:14 +00:00
|
|
|
.resolve(self, context)
|
|
|
|
.ok()
|
|
|
|
.unwrap_or(Value::Undefined)
|
|
|
|
.clone()
|
|
|
|
.into_string(),
|
|
|
|
);
|
2019-09-03 01:49:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
form_values
|
|
|
|
}
|
|
|
|
|
2019-09-19 00:47:21 +00:00
|
|
|
/// Add a stack frame that executes code in timeline scope
|
2019-10-06 21:45:14 +00:00
|
|
|
pub fn insert_stack_frame_for_action(
|
|
|
|
&mut self,
|
2019-12-09 21:39:53 +00:00
|
|
|
active_clip: DisplayObject<'gc>,
|
2019-10-06 21:45:14 +00:00
|
|
|
swf_version: u8,
|
|
|
|
code: SwfSlice,
|
2019-10-27 18:58:30 +00:00
|
|
|
action_context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
) {
|
|
|
|
let global_scope = GcCell::allocate(
|
|
|
|
action_context.gc_context,
|
|
|
|
Scope::from_global_object(self.globals),
|
|
|
|
);
|
2019-12-09 21:39:53 +00:00
|
|
|
let clip_obj = active_clip.object().as_object().unwrap();
|
2019-10-06 21:45:14 +00:00
|
|
|
let child_scope = GcCell::allocate(
|
|
|
|
action_context.gc_context,
|
|
|
|
Scope::new(global_scope, scope::ScopeClass::Target, clip_obj),
|
|
|
|
);
|
2019-10-10 02:58:53 +00:00
|
|
|
self.stack_frames.push(GcCell::allocate(
|
|
|
|
action_context.gc_context,
|
2019-11-03 22:43:28 +00:00
|
|
|
Activation::from_action(
|
|
|
|
swf_version,
|
|
|
|
code,
|
|
|
|
child_scope,
|
|
|
|
self.constant_pool,
|
2019-12-19 23:42:26 +00:00
|
|
|
active_clip,
|
2019-11-03 22:43:28 +00:00
|
|
|
clip_obj,
|
|
|
|
None,
|
|
|
|
),
|
2019-10-06 21:45:14 +00:00
|
|
|
));
|
2019-09-19 00:47:21 +00:00
|
|
|
}
|
|
|
|
|
2019-10-29 04:07:47 +00:00
|
|
|
/// Add a stack frame that executes code in initializer scope
|
|
|
|
pub fn insert_stack_frame_for_init_action(
|
|
|
|
&mut self,
|
2019-12-09 21:39:53 +00:00
|
|
|
active_clip: DisplayObject<'gc>,
|
2019-10-29 04:07:47 +00:00
|
|
|
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),
|
|
|
|
);
|
2019-12-09 21:39:53 +00:00
|
|
|
let clip_obj = active_clip.object().as_object().unwrap();
|
2019-10-29 04:07:47 +00:00
|
|
|
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,
|
2019-11-03 22:43:28 +00:00
|
|
|
Activation::from_action(
|
|
|
|
swf_version,
|
|
|
|
code,
|
|
|
|
child_scope,
|
|
|
|
self.constant_pool,
|
2019-12-19 23:42:26 +00:00
|
|
|
active_clip,
|
2019-11-03 22:43:28 +00:00
|
|
|
clip_obj,
|
|
|
|
None,
|
|
|
|
),
|
2019-10-29 04:07:47 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2019-12-16 02:10:28 +00:00
|
|
|
/// Add a stack frame that executes code in timeline scope for an event handler.
|
2019-12-18 19:30:21 +00:00
|
|
|
pub fn insert_stack_frame_for_avm_function(
|
2019-12-16 02:10:28 +00:00
|
|
|
&mut self,
|
|
|
|
active_clip: DisplayObject<'gc>,
|
|
|
|
swf_version: u8,
|
2019-12-18 19:30:21 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
name: &str,
|
2019-12-16 02:10:28 +00:00
|
|
|
) {
|
2019-12-18 19:30:21 +00:00
|
|
|
// Grab the property with the given name.
|
|
|
|
// Requires a dummy stack frame.
|
|
|
|
let clip = active_clip.object().as_object();
|
|
|
|
if let Ok(clip) = clip {
|
|
|
|
self.stack_frames.push(GcCell::allocate(
|
|
|
|
context.gc_context,
|
2019-12-19 23:42:26 +00:00
|
|
|
Activation::from_nothing(
|
|
|
|
swf_version,
|
|
|
|
self.globals,
|
|
|
|
context.gc_context,
|
|
|
|
active_clip,
|
|
|
|
),
|
2019-12-18 19:30:21 +00:00
|
|
|
));
|
|
|
|
let callback = clip
|
|
|
|
.get(name, self, context)
|
|
|
|
.and_then(|prop| prop.resolve(self, context));
|
|
|
|
self.stack_frames.pop();
|
|
|
|
|
|
|
|
// Run the callback.
|
|
|
|
// The function exec pushes its own stack frame.
|
|
|
|
// The function is now ready to execute with `run_stack_till_empty`.
|
|
|
|
if let Ok(callback) = callback {
|
|
|
|
let _ = callback.call(self, context, clip, &[]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-01 02:12:58 +00:00
|
|
|
/// Add a stack frame for any arbitrary code.
|
2019-10-21 22:37:04 +00:00
|
|
|
pub fn insert_stack_frame(&mut self, frame: GcCell<'gc, Activation<'gc>>) {
|
|
|
|
self.stack_frames.push(frame);
|
2019-09-29 03:11:03 +00:00
|
|
|
}
|
|
|
|
|
2019-09-15 19:25:34 +00:00
|
|
|
/// Retrieve the current AVM execution frame.
|
2019-10-06 21:45:14 +00:00
|
|
|
///
|
2019-09-15 19:25:34 +00:00
|
|
|
/// Yields None if there is no stack frame.
|
2019-10-10 02:58:53 +00:00
|
|
|
pub fn current_stack_frame(&self) -> Option<GcCell<'gc, Activation<'gc>>> {
|
2019-10-13 21:58:21 +00:00
|
|
|
self.stack_frames.last().copied()
|
2019-09-15 19:25:34 +00:00
|
|
|
}
|
|
|
|
|
2019-10-11 22:30:59 +00:00
|
|
|
/// Get the currently executing SWF version.
|
2019-10-13 22:55:39 +00:00
|
|
|
pub fn current_swf_version(&self) -> u8 {
|
2019-10-11 22:30:59 +00:00
|
|
|
self.current_stack_frame()
|
|
|
|
.map(|sf| sf.read().swf_version())
|
2019-10-13 22:55:39 +00:00
|
|
|
.unwrap_or(self.player_version)
|
2019-09-15 19:25:34 +00:00
|
|
|
}
|
2019-09-14 23:12:12 +00:00
|
|
|
|
2019-12-18 16:45:20 +00:00
|
|
|
pub fn notify_system_listeners(
|
|
|
|
&mut self,
|
|
|
|
active_clip: DisplayObject<'gc>,
|
|
|
|
swf_version: u8,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
listener: SystemListener,
|
|
|
|
method: &str,
|
|
|
|
args: &[Value<'gc>],
|
|
|
|
) {
|
|
|
|
// Push a dummy stack frame.
|
|
|
|
self.stack_frames.push(GcCell::allocate(
|
|
|
|
context.gc_context,
|
2019-12-19 23:42:26 +00:00
|
|
|
Activation::from_nothing(swf_version, self.globals, context.gc_context, active_clip),
|
2019-12-18 16:45:20 +00:00
|
|
|
));
|
|
|
|
let listeners = self.system_listeners.get(listener);
|
|
|
|
let mut handlers = listeners.prepare_handlers(self, context, method);
|
|
|
|
self.stack_frames.pop();
|
|
|
|
|
|
|
|
// Each callback exec pushes its own stack frame.
|
|
|
|
// The functions are now ready to execute with `run_stack_till_empty`.
|
|
|
|
for (listener, handler) in handlers.drain(..) {
|
|
|
|
let _ = handler.call(self, context, listener, &args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-15 21:57:35 +00:00
|
|
|
/// Perform some action with the current stack frame's reader.
|
2019-10-06 21:45:14 +00:00
|
|
|
///
|
2019-09-15 21:57:35 +00:00
|
|
|
/// This function constructs a reader based off the current stack frame's
|
|
|
|
/// reader. You are permitted to mutate the stack frame as you wish. If the
|
|
|
|
/// stack frame we started with still exists in the same location on the
|
|
|
|
/// stack, it's PC will be updated to the Reader's current PC.
|
2019-10-06 21:45:14 +00:00
|
|
|
///
|
2019-09-15 21:57:35 +00:00
|
|
|
/// Stack frame identity (for the purpose of the above paragraph) is
|
|
|
|
/// determined by the data pointed to by the `SwfSlice` of a given frame.
|
2019-10-06 21:45:14 +00:00
|
|
|
///
|
2019-09-29 13:12:31 +00:00
|
|
|
/// # Warnings
|
2019-10-06 21:45:14 +00:00
|
|
|
///
|
2019-09-29 13:12:31 +00:00
|
|
|
/// It is incorrect to call this function multiple times in the same stack.
|
|
|
|
/// Doing so will result in any changes in duplicate readers being ignored.
|
|
|
|
/// Always pass the borrowed reader into functions that need it.
|
2019-10-10 02:58:53 +00:00
|
|
|
fn with_current_reader_mut<F, R>(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-10 02:58:53 +00:00
|
|
|
func: F,
|
2019-11-14 20:06:54 +00:00
|
|
|
) -> Result<R, Error>
|
2019-10-06 21:45:14 +00:00
|
|
|
where
|
2019-11-14 20:06:54 +00:00
|
|
|
F: FnOnce(&mut Self, &mut Reader<'_>, &mut UpdateContext<'_, 'gc, '_>) -> Result<R, Error>,
|
2019-10-06 21:45:14 +00:00
|
|
|
{
|
2019-11-14 20:06:54 +00:00
|
|
|
let (frame_cell, swf_version, data, pc) = {
|
|
|
|
let frame = self.stack_frames.last().ok_or("No stack frame to read!")?;
|
2019-10-22 19:01:08 +00:00
|
|
|
let mut frame_ref = frame.write(context.gc_context);
|
2019-11-14 20:06:54 +00:00
|
|
|
frame_ref.lock()?;
|
2019-10-22 19:01:08 +00:00
|
|
|
|
2019-10-10 02:58:53 +00:00
|
|
|
(
|
2019-10-13 21:58:21 +00:00
|
|
|
*frame,
|
2019-10-13 21:54:09 +00:00
|
|
|
frame_ref.swf_version(),
|
|
|
|
frame_ref.data(),
|
|
|
|
frame_ref.pc(),
|
2019-10-10 02:58:53 +00:00
|
|
|
)
|
2019-11-14 20:06:54 +00:00
|
|
|
};
|
2019-10-22 19:01:08 +00:00
|
|
|
|
2019-09-15 21:57:35 +00:00
|
|
|
let mut read = Reader::new(data.as_ref(), swf_version);
|
|
|
|
read.seek(pc.try_into().unwrap());
|
|
|
|
|
2019-10-10 02:58:53 +00:00
|
|
|
let r = func(self, &mut read, context);
|
2019-09-15 21:57:35 +00:00
|
|
|
|
2019-10-22 19:01:08 +00:00
|
|
|
let mut frame_ref = frame_cell.write(context.gc_context);
|
|
|
|
frame_ref.unlock_execution();
|
|
|
|
frame_ref.set_pc(read.pos());
|
2019-10-02 21:33:33 +00:00
|
|
|
|
2019-11-14 20:06:54 +00:00
|
|
|
r
|
2019-09-15 19:25:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Destroy the current stack frame (if there is one).
|
2019-10-03 02:07:00 +00:00
|
|
|
///
|
2019-10-31 03:42:19 +00:00
|
|
|
/// The given return value will be pushed on the stack if there is a
|
|
|
|
/// function to return it to. Otherwise, it will be discarded.
|
2019-10-20 23:06:01 +00:00
|
|
|
///
|
2019-10-31 03:42:19 +00:00
|
|
|
/// NOTE: This means that if you are starting a brand new AVM stack just to
|
|
|
|
/// get it's return value, you won't get that value. Instead, retain a cell
|
|
|
|
/// referencing the oldest activation frame and use that to retrieve the
|
|
|
|
/// return value.
|
2019-10-17 18:40:55 +00:00
|
|
|
fn retire_stack_frame(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-18 19:10:26 +00:00
|
|
|
return_value: Value<'gc>,
|
|
|
|
) -> Result<(), Error> {
|
2019-10-03 02:07:00 +00:00
|
|
|
if let Some(frame) = self.current_stack_frame() {
|
2019-10-18 19:10:26 +00:00
|
|
|
self.stack_frames.pop();
|
|
|
|
|
2020-01-07 03:52:02 +00:00
|
|
|
let can_return = frame.read().can_return() && !self.stack_frames.is_empty();
|
2019-10-22 23:28:27 +00:00
|
|
|
if can_return {
|
2019-10-30 18:36:45 +00:00
|
|
|
frame
|
|
|
|
.write(context.gc_context)
|
|
|
|
.set_return_value(return_value.clone());
|
2019-12-18 19:30:21 +00:00
|
|
|
|
|
|
|
self.push(return_value);
|
2019-10-03 02:07:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-18 19:10:26 +00:00
|
|
|
Ok(())
|
2019-09-15 19:25:34 +00:00
|
|
|
}
|
2019-09-14 23:12:12 +00:00
|
|
|
|
2019-09-15 19:25:34 +00:00
|
|
|
/// Execute the AVM stack until it is exhausted.
|
2019-10-06 21:45:14 +00:00
|
|
|
pub fn run_stack_till_empty(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
while !self.stack_frames.is_empty() {
|
2019-10-10 02:58:53 +00:00
|
|
|
self.with_current_reader_mut(context, |this, r, context| {
|
|
|
|
this.do_next_action(context, r)
|
2019-11-14 20:06:54 +00:00
|
|
|
})?;
|
2019-10-22 19:01:08 +00:00
|
|
|
}
|
|
|
|
|
2019-12-18 19:33:50 +00:00
|
|
|
// Operand stack should be empty at this point.
|
|
|
|
// This is probably a bug on our part,
|
|
|
|
// although bytecode could in theory leave data on the stack.
|
|
|
|
if !self.stack.is_empty() {
|
|
|
|
log::warn!("Operand stack is not empty after execution");
|
|
|
|
self.stack.clear();
|
|
|
|
}
|
|
|
|
|
2019-10-22 19:01:08 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-31 03:42:19 +00:00
|
|
|
/// Execute the AVM stack until a given activation returns.
|
2019-10-22 19:01:08 +00:00
|
|
|
pub fn run_current_frame(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-22 19:18:40 +00:00
|
|
|
stop_frame: GcCell<'gc, Activation<'gc>>,
|
2019-10-22 19:01:08 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-10-22 19:18:40 +00:00
|
|
|
let mut stop_frame_id = None;
|
|
|
|
for (index, frame) in self.stack_frames.iter().enumerate() {
|
|
|
|
if GcCell::ptr_eq(stop_frame, *frame) {
|
|
|
|
stop_frame_id = Some(index);
|
|
|
|
}
|
2019-09-15 21:57:35 +00:00
|
|
|
}
|
|
|
|
|
2019-10-22 19:18:40 +00:00
|
|
|
if let Some(stop_frame_id) = stop_frame_id {
|
|
|
|
while self
|
|
|
|
.stack_frames
|
|
|
|
.get(stop_frame_id)
|
|
|
|
.map(|fr| GcCell::ptr_eq(stop_frame, *fr))
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
|
|
|
self.with_current_reader_mut(context, |this, r, context| {
|
|
|
|
this.do_next_action(context, r)
|
2019-11-14 20:06:54 +00:00
|
|
|
})?;
|
2019-10-22 19:18:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err("Attempted to run a frame not on the current interpreter stack".into())
|
|
|
|
}
|
2019-09-15 21:57:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Run a single action from a given action reader.
|
2019-10-06 21:45:14 +00:00
|
|
|
fn do_next_action(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
reader: &mut Reader<'_>,
|
|
|
|
) -> Result<(), Error> {
|
2019-10-10 02:58:53 +00:00
|
|
|
let data = self.current_stack_frame().unwrap().read().data();
|
2019-09-18 02:20:56 +00:00
|
|
|
|
|
|
|
if reader.pos() >= (data.end - data.start) {
|
|
|
|
//Executing beyond the end of a function constitutes an implicit return.
|
2019-10-18 19:10:26 +00:00
|
|
|
self.retire_stack_frame(context, Value::Undefined)?;
|
2019-09-18 02:20:56 +00:00
|
|
|
} else if let Some(action) = reader.read_action()? {
|
2019-11-26 20:16:48 +00:00
|
|
|
avm_debug!("Action: {:?}", action);
|
2019-11-20 23:08:50 +00:00
|
|
|
|
2019-09-15 21:57:35 +00:00
|
|
|
let result = match action {
|
|
|
|
Action::Add => self.action_add(context),
|
|
|
|
Action::Add2 => self.action_add_2(context),
|
|
|
|
Action::And => self.action_and(context),
|
|
|
|
Action::AsciiToChar => self.action_ascii_to_char(context),
|
|
|
|
Action::BitAnd => self.action_bit_and(context),
|
|
|
|
Action::BitLShift => self.action_bit_lshift(context),
|
|
|
|
Action::BitOr => self.action_bit_or(context),
|
|
|
|
Action::BitRShift => self.action_bit_rshift(context),
|
|
|
|
Action::BitURShift => self.action_bit_urshift(context),
|
|
|
|
Action::BitXor => self.action_bit_xor(context),
|
|
|
|
Action::Call => self.action_call(context),
|
|
|
|
Action::CallFunction => self.action_call_function(context),
|
|
|
|
Action::CallMethod => self.action_call_method(context),
|
2019-10-28 22:41:34 +00:00
|
|
|
Action::CastOp => self.action_cast_op(context),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::CharToAscii => self.action_char_to_ascii(context),
|
2019-10-06 21:45:14 +00:00
|
|
|
Action::CloneSprite => self.action_clone_sprite(context),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::ConstantPool(constant_pool) => {
|
|
|
|
self.action_constant_pool(context, &constant_pool[..])
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::Decrement => self.action_decrement(context),
|
|
|
|
Action::DefineFunction {
|
|
|
|
name,
|
|
|
|
params,
|
|
|
|
actions,
|
|
|
|
} => self.action_define_function(context, &name, ¶ms[..], actions),
|
2019-09-29 03:11:03 +00:00
|
|
|
Action::DefineFunction2(func) => self.action_define_function_2(context, &func),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::DefineLocal => self.action_define_local(context),
|
|
|
|
Action::DefineLocal2 => self.action_define_local_2(context),
|
|
|
|
Action::Delete => self.action_delete(context),
|
|
|
|
Action::Delete2 => self.action_delete_2(context),
|
|
|
|
Action::Divide => self.action_divide(context),
|
|
|
|
Action::EndDrag => self.action_end_drag(context),
|
|
|
|
Action::Enumerate => self.action_enumerate(context),
|
2019-10-26 16:06:05 +00:00
|
|
|
Action::Enumerate2 => self.action_enumerate_2(context),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::Equals => self.action_equals(context),
|
|
|
|
Action::Equals2 => self.action_equals_2(context),
|
2019-10-25 22:03:03 +00:00
|
|
|
Action::Extends => self.action_extends(context),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::GetMember => self.action_get_member(context),
|
|
|
|
Action::GetProperty => self.action_get_property(context),
|
|
|
|
Action::GetTime => self.action_get_time(context),
|
|
|
|
Action::GetVariable => self.action_get_variable(context),
|
|
|
|
Action::GetUrl { url, target } => self.action_get_url(context, &url, &target),
|
|
|
|
Action::GetUrl2 {
|
|
|
|
send_vars_method,
|
|
|
|
is_target_sprite,
|
|
|
|
is_load_vars,
|
|
|
|
} => {
|
|
|
|
self.action_get_url_2(context, send_vars_method, is_target_sprite, is_load_vars)
|
|
|
|
}
|
|
|
|
Action::GotoFrame(frame) => self.action_goto_frame(context, frame),
|
|
|
|
Action::GotoFrame2 {
|
|
|
|
set_playing,
|
|
|
|
scene_offset,
|
|
|
|
} => self.action_goto_frame_2(context, set_playing, scene_offset),
|
2019-10-08 06:06:05 +00:00
|
|
|
Action::Greater => self.action_greater(context),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::GotoLabel(label) => self.action_goto_label(context, &label),
|
2019-09-26 00:25:55 +00:00
|
|
|
Action::If { offset } => self.action_if(context, offset, reader),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::Increment => self.action_increment(context),
|
|
|
|
Action::InitArray => self.action_init_array(context),
|
|
|
|
Action::InitObject => self.action_init_object(context),
|
2019-10-28 22:30:22 +00:00
|
|
|
Action::ImplementsOp => self.action_implements_op(context),
|
2019-10-28 18:43:19 +00:00
|
|
|
Action::InstanceOf => self.action_instance_of(context),
|
2019-09-26 00:25:55 +00:00
|
|
|
Action::Jump { offset } => self.action_jump(context, offset, reader),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::Less => self.action_less(context),
|
|
|
|
Action::Less2 => self.action_less_2(context),
|
|
|
|
Action::MBAsciiToChar => self.action_mb_ascii_to_char(context),
|
|
|
|
Action::MBCharToAscii => self.action_mb_char_to_ascii(context),
|
|
|
|
Action::MBStringLength => self.action_mb_string_length(context),
|
|
|
|
Action::MBStringExtract => self.action_mb_string_extract(context),
|
|
|
|
Action::Modulo => self.action_modulo(context),
|
|
|
|
Action::Multiply => self.action_multiply(context),
|
|
|
|
Action::NextFrame => self.action_next_frame(context),
|
|
|
|
Action::NewMethod => self.action_new_method(context),
|
|
|
|
Action::NewObject => self.action_new_object(context),
|
|
|
|
Action::Not => self.action_not(context),
|
|
|
|
Action::Or => self.action_or(context),
|
|
|
|
Action::Play => self.action_play(context),
|
|
|
|
Action::Pop => self.action_pop(context),
|
|
|
|
Action::PreviousFrame => self.action_prev_frame(context),
|
|
|
|
Action::Push(values) => self.action_push(context, &values[..]),
|
|
|
|
Action::PushDuplicate => self.action_push_duplicate(context),
|
|
|
|
Action::RandomNumber => self.action_random_number(context),
|
|
|
|
Action::RemoveSprite => self.action_remove_sprite(context),
|
|
|
|
Action::Return => self.action_return(context),
|
|
|
|
Action::SetMember => self.action_set_member(context),
|
|
|
|
Action::SetProperty => self.action_set_property(context),
|
|
|
|
Action::SetTarget(target) => self.action_set_target(context, &target),
|
|
|
|
Action::SetTarget2 => self.action_set_target2(context),
|
|
|
|
Action::SetVariable => self.action_set_variable(context),
|
|
|
|
Action::StackSwap => self.action_stack_swap(context),
|
|
|
|
Action::StartDrag => self.action_start_drag(context),
|
|
|
|
Action::Stop => self.action_stop(context),
|
|
|
|
Action::StopSounds => self.action_stop_sounds(context),
|
|
|
|
Action::StoreRegister(register) => self.action_store_register(context, register),
|
2019-10-08 06:06:05 +00:00
|
|
|
Action::StrictEquals => self.action_strict_equals(context),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::StringAdd => self.action_string_add(context),
|
|
|
|
Action::StringEquals => self.action_string_equals(context),
|
|
|
|
Action::StringExtract => self.action_string_extract(context),
|
2019-10-08 06:06:05 +00:00
|
|
|
Action::StringGreater => self.action_string_greater(context),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::StringLength => self.action_string_length(context),
|
|
|
|
Action::StringLess => self.action_string_less(context),
|
|
|
|
Action::Subtract => self.action_subtract(context),
|
|
|
|
Action::TargetPath => self.action_target_path(context),
|
|
|
|
Action::ToggleQuality => self.toggle_quality(context),
|
|
|
|
Action::ToInteger => self.action_to_integer(context),
|
|
|
|
Action::ToNumber => self.action_to_number(context),
|
|
|
|
Action::ToString => self.action_to_string(context),
|
|
|
|
Action::Trace => self.action_trace(context),
|
|
|
|
Action::TypeOf => self.action_type_of(context),
|
|
|
|
Action::WaitForFrame {
|
|
|
|
frame,
|
|
|
|
num_actions_to_skip,
|
2019-09-29 13:12:31 +00:00
|
|
|
} => self.action_wait_for_frame(context, frame, num_actions_to_skip, reader),
|
2019-09-15 21:57:35 +00:00
|
|
|
Action::WaitForFrame2 {
|
|
|
|
num_actions_to_skip,
|
2019-09-29 13:12:31 +00:00
|
|
|
} => self.action_wait_for_frame_2(context, num_actions_to_skip, reader),
|
2019-09-23 01:10:49 +00:00
|
|
|
Action::With { actions } => self.action_with(context, actions),
|
2019-09-15 21:57:35 +00:00
|
|
|
_ => self.unknown_op(context, action),
|
|
|
|
};
|
|
|
|
if let Err(ref e) = result {
|
|
|
|
log::error!("AVM1 error: {}", e);
|
|
|
|
return result;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2019-09-15 21:57:35 +00:00
|
|
|
} else {
|
2019-09-18 02:20:56 +00:00
|
|
|
//The explicit end opcode was encountered so return here
|
2019-10-18 19:10:26 +00:00
|
|
|
self.retire_stack_frame(context, Value::Undefined)?;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-01-23 02:14:23 +00:00
|
|
|
/// Resolves a target value to a display object.
|
|
|
|
///
|
|
|
|
/// This is used by any action/function with a parameter that can be either
|
|
|
|
/// a display object or a string path referencing the display object.
|
|
|
|
/// For example, `removeMovieClip(mc)` takes either a string or a display object.
|
|
|
|
///
|
|
|
|
/// This can be an object, dot path, slash path, or weird combination thereof:
|
|
|
|
/// `_root/movieClip`, `movieClip.child._parent`, `movieClip:child`, etc.
|
|
|
|
/// See the `target_path` test for many examples.
|
|
|
|
///
|
|
|
|
/// A target path always resolves via the display list. It can look
|
|
|
|
/// at the prototype chain, but not the scope chain.
|
|
|
|
pub fn resolve_target_display_object(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
target: Value<'gc>,
|
|
|
|
) -> Result<Option<DisplayObject<'gc>>, Error> {
|
|
|
|
// If the value you got was a display object, we can just toss it straight back.
|
|
|
|
if let Value::Object(o) = target {
|
|
|
|
if let Some(o) = o.as_display_object() {
|
|
|
|
return Ok(Some(o));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we coerce it into a string and try to resolve it as a path.
|
|
|
|
// This means that values like `undefined` will resolve to clips with an instance name of
|
|
|
|
// `"undefined"`, for example.
|
|
|
|
let path = target.coerce_to_string(self, context)?;
|
|
|
|
let base_clip = self.target_clip_or_root(context);
|
|
|
|
Ok(self
|
|
|
|
.resolve_target_path(context, base_clip, &path)?
|
|
|
|
.and_then(|o| o.as_display_object()))
|
2019-09-22 18:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-01-23 02:14:23 +00:00
|
|
|
/// Resolves a target path string to an object.
|
|
|
|
/// This only returns `Object`; other values will bail out with `None`.
|
|
|
|
///
|
|
|
|
/// This can be a dot path, slash path, or weird combination thereof:
|
|
|
|
/// `_root/movieClip`, `movieClip.child._parent`, `movieClip:child`, etc.
|
|
|
|
/// See the `target_path` test for many examples.
|
|
|
|
///
|
|
|
|
/// A target path always resolves via the display list. It can look
|
|
|
|
/// at the prototype chain, but not the scope chain.
|
|
|
|
pub fn resolve_target_path(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-12-07 02:29:36 +00:00
|
|
|
start: DisplayObject<'gc>,
|
2020-01-23 02:14:23 +00:00
|
|
|
path: &str,
|
|
|
|
) -> Result<Option<Object<'gc>>, Error> {
|
|
|
|
let root = context.root;
|
|
|
|
|
|
|
|
// Empty path resolves immediately to start clip.
|
|
|
|
if path.is_empty() {
|
|
|
|
return Ok(Some(start.object().as_object().unwrap()));
|
|
|
|
}
|
|
|
|
|
2019-12-17 11:57:41 +00:00
|
|
|
// Starting / means an absolute path starting from root.
|
2020-01-23 02:14:23 +00:00
|
|
|
// (`/bar` means `_root.bar`)
|
|
|
|
let mut path = path.as_bytes();
|
|
|
|
let (clip, mut is_slash_path) = if path[0] == b'/' {
|
2019-08-26 23:38:37 +00:00
|
|
|
path = &path[1..];
|
2020-01-23 02:14:23 +00:00
|
|
|
(root, true)
|
2019-08-26 23:38:37 +00:00
|
|
|
} else {
|
2020-01-23 02:14:23 +00:00
|
|
|
(start, false)
|
2019-08-26 23:38:37 +00:00
|
|
|
};
|
2020-01-23 02:14:23 +00:00
|
|
|
let mut object = clip.object().as_object().unwrap();
|
|
|
|
|
|
|
|
// Iterate through each token in the path.
|
|
|
|
while !path.is_empty() {
|
|
|
|
// Skip any number of leading :
|
|
|
|
// `foo`, `:foo`, and `:::foo` are all the same
|
|
|
|
while path.get(0) == Some(&b':') {
|
|
|
|
path = &path[1..];
|
2019-12-17 11:57:41 +00:00
|
|
|
}
|
2020-01-23 02:14:23 +00:00
|
|
|
|
|
|
|
let val = if let b".." | b"../" | b"..:" = &path[..std::cmp::min(path.len(), 3)] {
|
|
|
|
// Check for ..
|
|
|
|
// SWF-4 style _parent
|
|
|
|
if path.get(2) == Some(&b'/') {
|
|
|
|
is_slash_path = true;
|
|
|
|
}
|
|
|
|
path = path.get(3..).unwrap_or(&[]);
|
|
|
|
if let Some(parent) = object.as_display_object().and_then(|o| o.parent()) {
|
|
|
|
parent.object()
|
|
|
|
} else {
|
|
|
|
// Tried to get parent of root, bail out.
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Step until the next delimiter.
|
|
|
|
// : . / all act as path delimiters.
|
|
|
|
// The only restriction is that after a / appears,
|
|
|
|
// . is no longer considered a delimiter.
|
|
|
|
// TODO: SWF4 is probably more restrictive.
|
|
|
|
let mut pos = 0;
|
|
|
|
while pos < path.len() {
|
|
|
|
match path[pos] {
|
|
|
|
b':' => break,
|
|
|
|
b'.' if !is_slash_path => break,
|
|
|
|
b'/' => {
|
|
|
|
is_slash_path = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
pos += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slice out the identifier and step the cursor past the delimiter.
|
|
|
|
let ident = &path[..pos];
|
|
|
|
path = path.get(pos + 1..).unwrap_or(&[]);
|
|
|
|
|
|
|
|
// Guaranteed to be valid UTF-8.
|
|
|
|
let name = unsafe { std::str::from_utf8_unchecked(ident) };
|
|
|
|
|
|
|
|
// Get the value from the object.
|
|
|
|
// Resolves display object instances first, then local variables.
|
|
|
|
// This is the opposite of general GetMember property access!
|
|
|
|
if let Some(child) = object
|
|
|
|
.as_display_object()
|
|
|
|
.and_then(|o| o.get_child_by_name(name))
|
|
|
|
{
|
|
|
|
child.object()
|
2019-08-26 23:38:37 +00:00
|
|
|
} else {
|
2020-01-23 02:14:23 +00:00
|
|
|
object
|
|
|
|
.get(&name, self, context)
|
|
|
|
.unwrap()
|
|
|
|
.resolve(self, context)?
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Resolve the value to an object while traversing the path.
|
|
|
|
object = if let Value::Object(o) = val {
|
|
|
|
o
|
|
|
|
} else {
|
|
|
|
return Ok(None);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Some(object))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the value referenced by a target path string.
|
|
|
|
///
|
|
|
|
/// This can be a raw variable name, a slash path, a dot path, or weird combination thereof.
|
|
|
|
/// For example:
|
|
|
|
/// `_root/movieClip.foo`, `movieClip:child:_parent`, `blah`
|
|
|
|
/// See the `target_path` test for many examples.
|
|
|
|
///
|
|
|
|
/// The string first tries to resolve as a variable or target path. The right-most : or .
|
|
|
|
/// delimits the variable name, with the left side identifying the target object path.
|
|
|
|
///
|
|
|
|
/// If there is no variable name, the string will try to resolve as a target path using
|
|
|
|
/// `resolve_target_path`.
|
|
|
|
///
|
|
|
|
/// Finally, if the above fails, it is a normal variable resovled via active stack frame
|
|
|
|
/// the scope chain.
|
|
|
|
pub fn get_variable<'s>(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
path: &'s str,
|
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
|
|
|
// Resolve a variable path for a GetVariable action.
|
|
|
|
let root = context.root;
|
|
|
|
let start = self.target_clip().unwrap_or(root);
|
|
|
|
|
|
|
|
// Find the right-most : or . in the path.
|
|
|
|
// If we have one, we must resolve as a target path.
|
|
|
|
// We also check for a / to skip some unnecessary work later.
|
|
|
|
let mut has_slash = false;
|
|
|
|
let mut var_iter = path.as_bytes().rsplitn(2, |c| match c {
|
|
|
|
b':' | b'.' => true,
|
|
|
|
b'/' => {
|
|
|
|
has_slash = true;
|
|
|
|
false
|
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
});
|
|
|
|
let b = var_iter.next();
|
|
|
|
let a = var_iter.next();
|
|
|
|
|
|
|
|
if let (Some(path), Some(var_name)) = (a, b) {
|
|
|
|
// We have a . or :, so this is a path to an object plus a variable name.
|
|
|
|
// We resolve it directly on the targeted object.
|
|
|
|
let path = unsafe { std::str::from_utf8_unchecked(path) };
|
|
|
|
let var_name = unsafe { std::str::from_utf8_unchecked(var_name) };
|
|
|
|
if let Some(object) = self.resolve_target_path(context, start, path)? {
|
|
|
|
return object.get(var_name, self, context);
|
|
|
|
} else {
|
|
|
|
return Ok(Value::Undefined.into());
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-23 02:14:23 +00:00
|
|
|
|
|
|
|
// If it doesn't have a trailing variable, it can still be a slash path.
|
|
|
|
// We can skip this step if we didn't find a slash above.
|
|
|
|
if has_slash {
|
|
|
|
if let Some(node) = self.resolve_target_path(context, start, path)? {
|
|
|
|
return Ok(node.into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally! It's a plain old variable name.
|
|
|
|
// Resolve using scope chain, as normal.
|
|
|
|
self.current_stack_frame()
|
|
|
|
.unwrap()
|
|
|
|
.read()
|
|
|
|
.resolve(&path, self, context)
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2020-01-23 02:14:23 +00:00
|
|
|
/// Sets the value referenced by a target path string.
|
|
|
|
///
|
|
|
|
/// This can be a raw variable name, a slash path, a dot path, or weird combination thereof.
|
|
|
|
/// For example:
|
|
|
|
/// `_root/movieClip.foo`, `movieClip:child:_parent`, `blah`
|
|
|
|
/// See the `target_path` test for many examples.
|
|
|
|
///
|
|
|
|
/// The string first tries to resolve as a variable or target path. The right-most : or .
|
|
|
|
/// delimits the variable name, with the left side identifying the target object path.
|
|
|
|
///
|
|
|
|
/// If there is no variable name, the entire string is considered the name, and it is
|
|
|
|
/// resovled normally via active stack frame and the scope chain.
|
|
|
|
///
|
|
|
|
/// This differs from `get_variable` because slash paths with no variable segment are invalid;
|
|
|
|
/// For example, `foo/bar` sets a property named `foo/bar` on the current stack frame instead
|
|
|
|
/// of drilling into the display list.
|
|
|
|
pub fn set_variable<'s>(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-08-26 23:38:37 +00:00
|
|
|
path: &'s str,
|
2020-01-23 02:14:23 +00:00
|
|
|
value: Value<'gc>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Resolve a variable path for a GetVariable action.
|
|
|
|
let root = context.root;
|
|
|
|
let start = self.target_clip().unwrap_or(root);
|
|
|
|
|
2019-09-17 09:00:57 +00:00
|
|
|
// If the target clip is invalid, we default to root for the variable path.
|
2020-01-23 02:14:23 +00:00
|
|
|
if path.is_empty() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the right-most : or . in the path.
|
|
|
|
// If we have one, we must resolve as a target path.
|
|
|
|
let mut var_iter = path.as_bytes().rsplitn(2, |&c| c == b':' || c == b'.');
|
|
|
|
let b = var_iter.next();
|
|
|
|
let a = var_iter.next();
|
|
|
|
|
|
|
|
if let (Some(path), Some(var_name)) = (a, b) {
|
|
|
|
// We have a . or :, so this is a path to an object plus a variable name.
|
|
|
|
// We resolve it directly on the targeted object.
|
|
|
|
let path = unsafe { std::str::from_utf8_unchecked(path) };
|
|
|
|
let var_name = unsafe { std::str::from_utf8_unchecked(var_name) };
|
|
|
|
if let Some(object) = self.resolve_target_path(context, start, path)? {
|
|
|
|
object.set(var_name, value, self, context)?;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2020-01-23 02:14:23 +00:00
|
|
|
return Ok(());
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2020-01-23 02:14:23 +00:00
|
|
|
// Finally! It's a plain old variable name.
|
|
|
|
// Set using scope chain, as normal.
|
|
|
|
// This will overwrite the value if the property exists somewhere
|
|
|
|
// in the scope chain, otherwise it is created on the top-level object.
|
|
|
|
let sf = self.current_stack_frame().unwrap();
|
|
|
|
let stack_frame = sf.read();
|
|
|
|
let this = stack_frame.this_cell();
|
|
|
|
let scope = stack_frame.scope_cell();
|
|
|
|
scope.read().set(path, value, self, context, this)?;
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 22:53:50 +00:00
|
|
|
fn push(&mut self, value: impl Into<Value<'gc>>) {
|
2019-11-20 23:08:50 +00:00
|
|
|
let value = value.into();
|
2019-11-26 20:16:48 +00:00
|
|
|
avm_debug!("Stack push {}: {:?}", self.stack.len(), value);
|
2019-11-20 23:08:50 +00:00
|
|
|
self.stack.push(value);
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2020-01-07 23:59:14 +00:00
|
|
|
#[allow(clippy::let_and_return)]
|
|
|
|
fn pop(&mut self) -> Value<'gc> {
|
|
|
|
let value = self.stack.pop().unwrap_or_else(|| {
|
|
|
|
log::warn!("Avm1::pop: Stack underflow");
|
|
|
|
Value::Undefined
|
|
|
|
});
|
|
|
|
|
|
|
|
avm_debug!("Stack pop {}: {:?}", self.stack.len(), value);
|
|
|
|
|
|
|
|
value
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-09-29 03:11:03 +00:00
|
|
|
/// Retrieve a given register value.
|
2019-10-06 21:45:14 +00:00
|
|
|
///
|
2019-09-29 03:11:03 +00:00
|
|
|
/// If a given register does not exist, this function yields
|
|
|
|
/// Value::Undefined, which is also a valid register value.
|
|
|
|
pub fn current_register(&self, id: u8) -> Value<'gc> {
|
2019-10-06 21:45:14 +00:00
|
|
|
if self
|
|
|
|
.current_stack_frame()
|
2019-10-13 22:41:07 +00:00
|
|
|
.map(|sf| sf.read().has_local_register(id))
|
2019-10-06 21:45:14 +00:00
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
2019-10-10 02:58:53 +00:00
|
|
|
self.current_stack_frame()
|
|
|
|
.unwrap()
|
|
|
|
.read()
|
|
|
|
.local_register(id)
|
2019-10-13 22:41:07 +00:00
|
|
|
.unwrap_or(Value::Undefined)
|
2019-09-27 00:02:40 +00:00
|
|
|
} else {
|
2019-10-06 21:45:14 +00:00
|
|
|
self.registers
|
|
|
|
.get(id as usize)
|
|
|
|
.cloned()
|
|
|
|
.unwrap_or(Value::Undefined)
|
2019-09-27 00:02:40 +00:00
|
|
|
}
|
2019-09-25 22:59:02 +00:00
|
|
|
}
|
|
|
|
|
2019-09-29 03:11:03 +00:00
|
|
|
/// Set a register to a given value.
|
2019-10-06 21:45:14 +00:00
|
|
|
///
|
2019-09-29 03:11:03 +00:00
|
|
|
/// If a given register does not exist, this function does nothing.
|
2019-10-06 21:45:14 +00:00
|
|
|
pub fn set_current_register(
|
|
|
|
&mut self,
|
|
|
|
id: u8,
|
|
|
|
value: Value<'gc>,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
) {
|
|
|
|
if self
|
|
|
|
.current_stack_frame()
|
2019-10-13 22:41:07 +00:00
|
|
|
.map(|sf| sf.read().has_local_register(id))
|
2019-10-06 21:45:14 +00:00
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
2019-10-10 02:58:53 +00:00
|
|
|
self.current_stack_frame()
|
|
|
|
.unwrap()
|
|
|
|
.write(context.gc_context)
|
|
|
|
.set_local_register(id, value, context.gc_context);
|
2019-10-06 21:45:14 +00:00
|
|
|
} else if let Some(v) = self.registers.get_mut(id as usize) {
|
|
|
|
*v = value;
|
2019-09-27 00:02:40 +00:00
|
|
|
}
|
2019-09-25 22:59:02 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
fn unknown_op(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
_context: &mut UpdateContext,
|
2019-08-26 23:38:37 +00:00
|
|
|
action: swf::avm1::types::Action,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
log::error!("Unknown AVM1 opcode: {:?}", action);
|
|
|
|
Err("Unknown op".into())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_add(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(b.into_number_v1() + a.into_number_v1());
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 00:35:22 +00:00
|
|
|
fn action_add_2(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// ECMA-262 s. 11.6.1
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-09-23 02:47:55 +00:00
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
// TODO(Herschel):
|
|
|
|
if let Value::String(a) = a {
|
2019-10-27 00:35:22 +00:00
|
|
|
let mut s = b.coerce_to_string(self, context)?;
|
2019-08-26 23:38:37 +00:00
|
|
|
s.push_str(&a);
|
2019-10-21 10:30:59 +00:00
|
|
|
self.push(s);
|
2019-08-26 23:38:37 +00:00
|
|
|
} else if let Value::String(mut b) = b {
|
2019-10-27 00:35:22 +00:00
|
|
|
b.push_str(&a.coerce_to_string(self, context)?);
|
2019-10-21 10:30:59 +00:00
|
|
|
self.push(b);
|
2019-08-26 23:38:37 +00:00
|
|
|
} else {
|
2019-10-27 00:35:22 +00:00
|
|
|
let result = b.as_number(self, context)? + a.as_number(self, context)?;
|
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_and(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// AS1 logical and
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-12-22 06:40:06 +00:00
|
|
|
let version = self.current_swf_version();
|
|
|
|
let result = b.as_bool(version) && a.as_bool(version);
|
|
|
|
self.push(Value::from_bool(result, self.current_swf_version()));
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_ascii_to_char(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// TODO(Herschel): Results on incorrect operands?
|
2020-01-07 23:59:14 +00:00
|
|
|
let val = (self.pop().as_f64()? as u8) as char;
|
2019-10-21 10:30:59 +00:00
|
|
|
self.push(val.to_string());
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_char_to_ascii(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// TODO(Herschel): Results on incorrect operands?
|
2020-01-07 23:59:14 +00:00
|
|
|
let s = self.pop().into_string();
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = s.bytes().nth(0).unwrap_or(0);
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-17 09:00:29 +00:00
|
|
|
fn action_clone_sprite(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let depth = self.pop();
|
|
|
|
let target = self.pop();
|
|
|
|
let source = self.pop();
|
2020-01-23 02:14:23 +00:00
|
|
|
let source_clip = self.resolve_target_display_object(context, source)?;
|
2019-12-17 09:00:29 +00:00
|
|
|
|
|
|
|
if let Some(movie_clip) = source_clip.and_then(|o| o.as_movie_clip()) {
|
|
|
|
let _ = globals::movie_clip::duplicate_movie_clip(
|
|
|
|
movie_clip,
|
|
|
|
self,
|
|
|
|
context,
|
|
|
|
&[target, depth],
|
|
|
|
0,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log::warn!("CloneSprite: Source is not a movie clip");
|
|
|
|
}
|
|
|
|
|
2019-09-28 00:28:14 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_bit_and(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_u32()?;
|
|
|
|
let b = self.pop().as_u32()?;
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = a & b;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_bit_lshift(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_i32()? & 0b11111; // Only 5 bits used for shift count
|
|
|
|
let b = self.pop().as_i32()?;
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = b << a;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_bit_or(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_u32()?;
|
|
|
|
let b = self.pop().as_u32()?;
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = a | b;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_bit_rshift(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_i32()? & 0b11111; // Only 5 bits used for shift count
|
|
|
|
let b = self.pop().as_i32()?;
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = b >> a;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_bit_urshift(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_u32()? & 0b11111; // Only 5 bits used for shift count
|
|
|
|
let b = self.pop().as_u32()?;
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = b >> a;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_bit_xor(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_u32()?;
|
|
|
|
let b = self.pop().as_u32()?;
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = b ^ a;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-01-14 07:37:57 +00:00
|
|
|
fn action_call(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
|
|
|
// Runs any actions on the given frame.
|
|
|
|
let frame = self.pop();
|
|
|
|
let clip = self.target_clip_or_root(context);
|
|
|
|
if let Some(clip) = clip.as_movie_clip() {
|
|
|
|
// Use frame # if parameter is a number, otherwise cast to string and check for frame labels.
|
|
|
|
let frame = if let Ok(frame) = frame.as_u32() {
|
|
|
|
if frame >= 1 && frame <= u32::from(clip.total_frames()) {
|
|
|
|
Some(frame as u16)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let frame_label = frame.coerce_to_string(self, context)?;
|
|
|
|
clip.frame_label_to_number(&frame_label)
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(frame) = frame {
|
|
|
|
// We must run the actions in the order that the tags appear,
|
|
|
|
// so we want to push the stack frames in reverse order.
|
|
|
|
for action in clip.actions_on_frame(context, frame).rev() {
|
|
|
|
self.insert_stack_frame_for_action(
|
|
|
|
self.target_clip_or_root(context),
|
|
|
|
self.current_swf_version(),
|
|
|
|
action,
|
|
|
|
context,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::warn!("Call: Invalid frame {:?}", frame);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::warn!("Call: Expected MovieClip");
|
|
|
|
}
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-06 21:45:14 +00:00
|
|
|
fn action_call_function(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let fn_name = self.pop();
|
2019-09-16 23:15:22 +00:00
|
|
|
let mut args = Vec::new();
|
2020-01-07 23:59:14 +00:00
|
|
|
let num_args = self.pop().as_i64()?; // TODO(Herschel): max arg count?
|
2019-08-26 23:38:37 +00:00
|
|
|
for _ in 0..num_args {
|
2020-01-07 23:59:14 +00:00
|
|
|
args.push(self.pop());
|
2019-09-16 23:15:22 +00:00
|
|
|
}
|
2019-10-06 21:45:14 +00:00
|
|
|
|
2019-10-23 01:53:33 +00:00
|
|
|
let target_fn = self
|
|
|
|
.stack_frames
|
|
|
|
.last()
|
|
|
|
.unwrap()
|
|
|
|
.clone()
|
|
|
|
.read()
|
2019-10-26 03:21:14 +00:00
|
|
|
.resolve(fn_name.as_string()?, self, context)?
|
2019-10-23 01:53:33 +00:00
|
|
|
.resolve(self, context)?;
|
2019-12-19 23:42:26 +00:00
|
|
|
let this = self.target_clip_or_root(context).object().as_object()?;
|
2019-10-23 01:53:33 +00:00
|
|
|
target_fn.call(self, context, this, &args)?.push(self);
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2019-09-16 23:15:22 +00:00
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-06 21:45:14 +00:00
|
|
|
fn action_call_method(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let method_name = self.pop();
|
2020-01-19 22:10:04 +00:00
|
|
|
let object_val = self.pop();
|
|
|
|
let object = value_object::ValueObject::boxed(self, context, object_val);
|
2020-01-07 23:59:14 +00:00
|
|
|
let num_args = self.pop().as_i64()?; // TODO(Herschel): max arg count?
|
2019-08-26 22:53:50 +00:00
|
|
|
let mut args = Vec::new();
|
2019-08-26 23:38:37 +00:00
|
|
|
for _ in 0..num_args {
|
2020-01-07 23:59:14 +00:00
|
|
|
args.push(self.pop());
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 22:53:50 +00:00
|
|
|
match method_name {
|
|
|
|
Value::Undefined | Value::Null => {
|
2019-12-19 23:42:26 +00:00
|
|
|
let this = self.target_clip_or_root(context).object();
|
2019-12-13 21:36:21 +00:00
|
|
|
if let Ok(this) = this.as_object() {
|
|
|
|
object.call(self, context, this, &args)?.push(self);
|
|
|
|
} else {
|
|
|
|
log::warn!(
|
|
|
|
"Attempted to call constructor of {:?} (missing method name)",
|
|
|
|
this
|
|
|
|
);
|
|
|
|
self.push(Value::Undefined);
|
|
|
|
}
|
2019-08-26 22:53:50 +00:00
|
|
|
}
|
|
|
|
Value::String(name) => {
|
|
|
|
if name.is_empty() {
|
2020-01-18 05:32:57 +00:00
|
|
|
object.call(self, context, object, &args)?.push(self);
|
|
|
|
} else {
|
|
|
|
let callable = object.get(&name, self, context)?.resolve(self, context)?;
|
2019-10-23 01:53:33 +00:00
|
|
|
|
2019-11-24 17:29:15 +00:00
|
|
|
if let Value::Object(_) = callable {
|
|
|
|
} else {
|
|
|
|
log::warn!("Object method {} is not callable", name);
|
2019-10-23 01:53:33 +00:00
|
|
|
}
|
|
|
|
|
2020-01-18 05:32:57 +00:00
|
|
|
callable.call(self, context, object, &args)?.push(self);
|
2019-08-26 22:53:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
return Err(format!(
|
|
|
|
"Invalid method name, expected string but found {:?}",
|
|
|
|
method_name
|
|
|
|
)
|
|
|
|
.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-28 22:41:34 +00:00
|
|
|
fn action_cast_op(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let obj = self.pop().as_object()?;
|
|
|
|
let constr = self.pop().as_object()?;
|
2019-10-28 22:41:34 +00:00
|
|
|
|
|
|
|
let prototype = constr
|
|
|
|
.get("prototype", self, context)?
|
|
|
|
.resolve(self, context)?
|
|
|
|
.as_object()?;
|
|
|
|
|
2019-11-30 05:30:10 +00:00
|
|
|
if obj.is_instance_of(self, context, constr, prototype)? {
|
2019-10-28 22:41:34 +00:00
|
|
|
self.push(obj);
|
|
|
|
} else {
|
|
|
|
self.push(Value::Null);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
fn action_constant_pool(
|
|
|
|
&mut self,
|
2019-11-03 22:43:28 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-09-09 20:57:29 +00:00
|
|
|
constant_pool: &[&str],
|
2019-08-26 23:38:37 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-11-03 22:43:28 +00:00
|
|
|
self.constant_pool = GcCell::allocate(
|
|
|
|
context.gc_context,
|
2019-12-19 17:10:41 +00:00
|
|
|
constant_pool.iter().map(|s| (*s).to_string()).collect(),
|
2019-11-03 22:43:28 +00:00
|
|
|
);
|
|
|
|
self.current_stack_frame()
|
|
|
|
.unwrap()
|
|
|
|
.write(context.gc_context)
|
|
|
|
.set_constant_pool(self.constant_pool);
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 00:35:22 +00:00
|
|
|
fn action_decrement(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_number(self, context)?;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(a - 1.0);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_define_function(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-09-16 05:37:15 +00:00
|
|
|
name: &str,
|
|
|
|
params: &[&str],
|
|
|
|
actions: &[u8],
|
2019-08-26 23:38:37 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-10-10 02:58:53 +00:00
|
|
|
let swf_version = self.current_stack_frame().unwrap().read().swf_version();
|
2019-10-06 21:45:14 +00:00
|
|
|
let func_data = self
|
|
|
|
.current_stack_frame()
|
|
|
|
.unwrap()
|
2019-10-10 02:58:53 +00:00
|
|
|
.read()
|
2019-10-06 21:45:14 +00:00
|
|
|
.data()
|
|
|
|
.to_subslice(actions)
|
|
|
|
.unwrap();
|
|
|
|
let scope = Scope::new_closure_scope(
|
2019-10-10 02:58:53 +00:00
|
|
|
self.current_stack_frame().unwrap().read().scope_cell(),
|
2019-10-06 21:45:14 +00:00
|
|
|
context.gc_context,
|
|
|
|
);
|
2019-11-03 22:43:28 +00:00
|
|
|
let constant_pool = self.current_stack_frame().unwrap().read().constant_pool();
|
2019-12-19 23:42:26 +00:00
|
|
|
let func = Avm1Function::from_df1(
|
|
|
|
swf_version,
|
|
|
|
func_data,
|
|
|
|
name,
|
|
|
|
params,
|
|
|
|
scope,
|
|
|
|
constant_pool,
|
|
|
|
self.target_clip_or_root(context),
|
|
|
|
);
|
2019-12-06 18:24:36 +00:00
|
|
|
let prototype =
|
|
|
|
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
|
2019-11-30 18:31:11 +00:00
|
|
|
let func_obj = FunctionObject::function(
|
2019-10-23 18:15:46 +00:00
|
|
|
context.gc_context,
|
|
|
|
func,
|
|
|
|
Some(self.prototypes.function),
|
|
|
|
Some(prototype),
|
|
|
|
);
|
2019-09-16 05:37:15 +00:00
|
|
|
if name == "" {
|
2019-10-08 22:54:58 +00:00
|
|
|
self.push(func_obj);
|
2019-09-16 05:37:15 +00:00
|
|
|
} else {
|
2019-10-10 02:58:53 +00:00
|
|
|
self.current_stack_frame()
|
2019-10-06 21:45:14 +00:00
|
|
|
.unwrap()
|
2019-10-10 02:58:53 +00:00
|
|
|
.read()
|
2019-10-08 22:54:58 +00:00
|
|
|
.define(name, func_obj, context.gc_context);
|
2019-09-16 05:37:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-09-29 03:11:03 +00:00
|
|
|
fn action_define_function_2(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
action_func: &Function,
|
2019-09-29 03:11:03 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-10-10 02:58:53 +00:00
|
|
|
let swf_version = self.current_stack_frame().unwrap().read().swf_version();
|
2019-10-06 21:45:14 +00:00
|
|
|
let func_data = self
|
|
|
|
.current_stack_frame()
|
|
|
|
.unwrap()
|
2019-10-10 02:58:53 +00:00
|
|
|
.read()
|
2019-10-06 21:45:14 +00:00
|
|
|
.data()
|
|
|
|
.to_subslice(action_func.actions)
|
|
|
|
.unwrap();
|
|
|
|
let scope = Scope::new_closure_scope(
|
2019-10-10 02:58:53 +00:00
|
|
|
self.current_stack_frame().unwrap().read().scope_cell(),
|
2019-10-06 21:45:14 +00:00
|
|
|
context.gc_context,
|
2019-09-29 03:11:03 +00:00
|
|
|
);
|
2019-11-03 22:43:28 +00:00
|
|
|
let constant_pool = self.current_stack_frame().unwrap().read().constant_pool();
|
2019-12-19 23:42:26 +00:00
|
|
|
let func = Avm1Function::from_df2(
|
|
|
|
swf_version,
|
|
|
|
func_data,
|
|
|
|
action_func,
|
|
|
|
scope,
|
|
|
|
constant_pool,
|
|
|
|
self.base_clip(),
|
|
|
|
);
|
2019-12-06 18:24:36 +00:00
|
|
|
let prototype =
|
|
|
|
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
|
2019-11-30 18:31:11 +00:00
|
|
|
let func_obj = FunctionObject::function(
|
2019-10-23 18:15:46 +00:00
|
|
|
context.gc_context,
|
|
|
|
func,
|
|
|
|
Some(self.prototypes.function),
|
|
|
|
Some(prototype),
|
|
|
|
);
|
2019-09-29 03:11:03 +00:00
|
|
|
if action_func.name == "" {
|
|
|
|
self.push(func_obj);
|
|
|
|
} else {
|
2019-10-10 02:58:53 +00:00
|
|
|
self.current_stack_frame().unwrap().read().define(
|
2019-10-06 21:45:14 +00:00
|
|
|
action_func.name,
|
|
|
|
func_obj,
|
|
|
|
context.gc_context,
|
|
|
|
);
|
2019-09-29 03:11:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-06 21:45:14 +00:00
|
|
|
fn action_define_local(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let value = self.pop();
|
|
|
|
let name = self.pop();
|
2019-10-10 02:58:53 +00:00
|
|
|
self.current_stack_frame().unwrap().read().define(
|
2019-10-06 21:45:14 +00:00
|
|
|
name.as_string()?,
|
|
|
|
value,
|
|
|
|
context.gc_context,
|
|
|
|
);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-06 21:45:14 +00:00
|
|
|
fn action_define_local_2(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let name = self.pop();
|
2019-10-10 02:58:53 +00:00
|
|
|
self.current_stack_frame().unwrap().read().define(
|
2019-10-06 21:45:14 +00:00
|
|
|
name.as_string()?,
|
|
|
|
Value::Undefined,
|
|
|
|
context.gc_context,
|
|
|
|
);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_delete(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let name_val = self.pop();
|
2019-09-23 18:33:44 +00:00
|
|
|
let name = name_val.as_string()?;
|
2020-01-07 23:59:14 +00:00
|
|
|
let object = self.pop().as_object()?;
|
2019-09-24 02:36:34 +00:00
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
let success = object.delete(context.gc_context, name);
|
2019-10-21 10:55:17 +00:00
|
|
|
self.push(success);
|
2019-09-23 18:33:44 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_delete_2(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let name_val = self.pop();
|
2019-09-23 18:33:44 +00:00
|
|
|
let name = name_val.as_string()?;
|
2019-09-24 02:36:34 +00:00
|
|
|
|
|
|
|
//Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns
|
|
|
|
//a boolean based on if the delete actually deleted something.
|
2019-10-21 10:55:17 +00:00
|
|
|
let did_exist = self.current_stack_frame().unwrap().read().is_defined(name);
|
2019-09-24 02:36:34 +00:00
|
|
|
|
2019-10-06 21:45:14 +00:00
|
|
|
self.current_stack_frame()
|
|
|
|
.unwrap()
|
2019-10-10 02:58:53 +00:00
|
|
|
.read()
|
2019-10-06 21:45:14 +00:00
|
|
|
.scope()
|
|
|
|
.delete(name, context.gc_context);
|
2019-09-24 02:36:34 +00:00
|
|
|
self.push(did_exist);
|
2019-09-23 18:33:44 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-12-22 11:01:58 +00:00
|
|
|
fn action_divide(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// AS1 divide
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_number(self, context)?;
|
|
|
|
let b = self.pop().as_number(self, context)?;
|
2019-08-26 23:38:37 +00:00
|
|
|
|
|
|
|
// TODO(Herschel): SWF19: "If A is zero, the result NaN, Infinity, or -Infinity is pushed to the in SWF 5 and later.
|
|
|
|
// In SWF 4, the result is the string #ERROR#.""
|
2019-09-28 00:58:40 +00:00
|
|
|
// Seems to be untrue for SWF v4, I get 1.#INF.
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2019-12-22 11:01:58 +00:00
|
|
|
self.push(b / a);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-21 23:37:27 +00:00
|
|
|
fn action_end_drag(&mut self, context: &mut UpdateContext) -> Result<(), Error> {
|
|
|
|
*context.drag_object = None;
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_enumerate(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let name_value = self.pop();
|
2019-10-19 02:53:29 +00:00
|
|
|
let name = name_value.as_string()?;
|
2019-08-26 23:38:37 +00:00
|
|
|
self.push(Value::Null); // Sentinel that indicates end of enumeration
|
2019-10-23 01:53:33 +00:00
|
|
|
let object = self
|
|
|
|
.current_stack_frame()
|
2019-10-10 02:58:53 +00:00
|
|
|
.unwrap()
|
|
|
|
.read()
|
2019-10-26 03:21:14 +00:00
|
|
|
.resolve(name, self, context)?
|
2019-10-23 01:53:33 +00:00
|
|
|
.resolve(self, context)?;
|
|
|
|
|
|
|
|
match object {
|
|
|
|
Value::Object(ob) => {
|
2019-12-06 18:24:36 +00:00
|
|
|
for k in ob.get_keys() {
|
2019-10-23 01:53:33 +00:00
|
|
|
self.push(k);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => log::error!("Cannot enumerate properties of {}", name_value.as_string()?),
|
|
|
|
};
|
2019-09-22 02:28:10 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-26 16:06:05 +00:00
|
|
|
fn action_enumerate_2(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let object = self.pop().as_object()?;
|
2019-10-26 16:06:05 +00:00
|
|
|
|
|
|
|
self.push(Value::Null); // Sentinel that indicates end of enumeration
|
2019-12-06 18:24:36 +00:00
|
|
|
for k in object.get_keys() {
|
2019-10-26 16:06:05 +00:00
|
|
|
self.push(k);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
#[allow(clippy::float_cmp)]
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_equals(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// AS1 equality
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = b.into_number_v1() == a.into_number_v1();
|
2019-12-22 06:40:06 +00:00
|
|
|
self.push(Value::from_bool(result, self.current_swf_version()));
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::float_cmp)]
|
2019-10-27 00:35:22 +00:00
|
|
|
fn action_equals_2(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// Version >=5 equality
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-11-22 21:00:13 +00:00
|
|
|
let result = b.abstract_eq(a, self, context, false)?;
|
2019-10-21 10:55:17 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-25 22:03:03 +00:00
|
|
|
fn action_extends(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let superclass = self.pop().as_object()?;
|
|
|
|
let subclass = self.pop().as_object()?;
|
2019-10-25 22:03:03 +00:00
|
|
|
|
2019-11-30 02:07:37 +00:00
|
|
|
//TODO: What happens if we try to extend an object which has no `prototype`?
|
|
|
|
//e.g. `class Whatever extends Object.prototype` or `class Whatever extends 5`
|
|
|
|
let super_proto = superclass
|
|
|
|
.get("prototype", self, context)?
|
|
|
|
.resolve(self, context)
|
|
|
|
.and_then(|val| val.as_object())
|
|
|
|
.unwrap_or(self.prototypes.object);
|
2019-10-25 22:03:03 +00:00
|
|
|
|
2019-10-28 22:13:29 +00:00
|
|
|
let sub_prototype: Object<'gc> =
|
|
|
|
ScriptObject::object(context.gc_context, Some(super_proto)).into();
|
2019-10-25 22:03:03 +00:00
|
|
|
|
2019-10-28 22:13:29 +00:00
|
|
|
sub_prototype.set("constructor", superclass.into(), self, context)?;
|
|
|
|
subclass.set("prototype", sub_prototype.into(), self, context)?;
|
2019-10-25 22:03:03 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_get_member(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let name_val = self.pop();
|
2019-10-27 00:35:22 +00:00
|
|
|
let name = name_val.coerce_to_string(self, context)?;
|
2020-01-19 22:10:04 +00:00
|
|
|
let object_val = self.pop();
|
|
|
|
let object = value_object::ValueObject::boxed(self, context, object_val);
|
2020-01-18 05:32:57 +00:00
|
|
|
|
|
|
|
object.get(&name, self, context)?.push(self);
|
2019-09-26 00:06:37 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-09-17 17:28:10 +00:00
|
|
|
fn action_get_property(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-09-17 17:28:10 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let prop_index = self.pop().into_number_v1() as usize;
|
2020-01-23 02:14:23 +00:00
|
|
|
let path = self.pop();
|
|
|
|
let ret = if self.target_clip().is_some() {
|
|
|
|
if let Some(clip) = self.resolve_target_display_object(context, path)? {
|
2019-12-04 01:52:00 +00:00
|
|
|
let display_properties = self.display_properties;
|
|
|
|
let props = display_properties.write(context.gc_context);
|
|
|
|
if let Some(property) = props.get_by_index(prop_index) {
|
|
|
|
property.get(self, context, clip)?
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
2019-12-04 01:52:00 +00:00
|
|
|
log::warn!("GetProperty: Invalid property index {}", prop_index);
|
2019-09-17 09:00:57 +00:00
|
|
|
Value::Undefined
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-12-22 06:40:06 +00:00
|
|
|
//log::warn!("GetProperty: Invalid target {}", path);
|
2019-08-26 23:38:37 +00:00
|
|
|
Value::Undefined
|
|
|
|
}
|
|
|
|
} else {
|
2019-09-17 09:00:57 +00:00
|
|
|
log::warn!("GetProperty: Invalid base clip");
|
2019-08-26 23:38:37 +00:00
|
|
|
Value::Undefined
|
|
|
|
};
|
|
|
|
self.push(ret);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_get_time(&mut self, context: &mut UpdateContext) -> Result<(), Error> {
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(context.global_time as f64);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-09-29 13:31:33 +00:00
|
|
|
/// Obtain the value of `_root`.
|
2019-10-27 18:58:30 +00:00
|
|
|
pub fn root_object(&self, context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> {
|
2019-12-07 02:29:36 +00:00
|
|
|
context.root.object()
|
2019-09-29 13:31:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Obtain the value of `_global`.
|
2019-10-27 18:58:30 +00:00
|
|
|
pub fn global_object(&self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> {
|
2019-09-29 13:31:33 +00:00
|
|
|
Value::Object(self.globals)
|
|
|
|
}
|
|
|
|
|
2019-10-04 02:42:32 +00:00
|
|
|
/// Obtain a reference to `_global`.
|
2019-12-06 18:24:36 +00:00
|
|
|
pub fn global_object_cell(&self) -> Object<'gc> {
|
2019-10-04 02:42:32 +00:00
|
|
|
self.globals
|
|
|
|
}
|
|
|
|
|
2019-10-17 02:31:41 +00:00
|
|
|
/// Obtain system built-in prototypes for this instance.
|
|
|
|
pub fn prototypes(&self) -> &globals::SystemPrototypes<'gc> {
|
|
|
|
&self.prototypes
|
|
|
|
}
|
|
|
|
|
2019-08-26 22:53:50 +00:00
|
|
|
fn action_get_variable(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-08-26 22:53:50 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let var_path = self.pop();
|
2020-01-23 02:14:23 +00:00
|
|
|
let path = var_path.coerce_to_string(self, context)?;
|
2019-08-26 22:53:50 +00:00
|
|
|
|
2020-01-23 02:14:23 +00:00
|
|
|
self.get_variable(context, &path)?.push(self);
|
2019-09-25 03:27:05 +00:00
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_get_url(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext,
|
2019-09-02 17:23:38 +00:00
|
|
|
url: &str,
|
|
|
|
target: &str,
|
2019-08-26 23:38:37 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-09-02 18:55:45 +00:00
|
|
|
//TODO: support `_level0` thru `_level9`
|
|
|
|
if target.starts_with("_level") {
|
2019-09-17 03:37:11 +00:00
|
|
|
log::warn!(
|
|
|
|
"Remote SWF loads into target {} not yet implemented",
|
|
|
|
target
|
|
|
|
);
|
2019-09-02 18:55:45 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2019-09-20 19:11:33 +00:00
|
|
|
if let Some(fscommand) = fscommand::parse(url) {
|
|
|
|
return fscommand::handle(fscommand, self, context);
|
|
|
|
}
|
|
|
|
|
2019-09-17 03:37:11 +00:00
|
|
|
context
|
|
|
|
.navigator
|
|
|
|
.navigate_to_url(url.to_owned(), Some(target.to_owned()), None);
|
2019-09-02 17:23:38 +00:00
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_get_url_2(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-09-03 01:49:36 +00:00
|
|
|
swf_method: swf::avm1::types::SendVarsMethod,
|
2019-09-02 17:23:38 +00:00
|
|
|
is_target_sprite: bool,
|
|
|
|
is_load_vars: bool,
|
2019-08-26 23:38:37 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-09-02 17:23:38 +00:00
|
|
|
// TODO: Support `LoadVariablesFlag`, `LoadTargetFlag`
|
|
|
|
// TODO: What happens if there's only one string?
|
2020-01-07 23:59:14 +00:00
|
|
|
let target = self.pop().into_string();
|
|
|
|
let url = self.pop().into_string();
|
2019-09-02 17:23:38 +00:00
|
|
|
|
2019-09-21 01:38:37 +00:00
|
|
|
if let Some(fscommand) = fscommand::parse(&url) {
|
|
|
|
return fscommand::handle(fscommand, self, context);
|
|
|
|
}
|
|
|
|
|
2019-09-02 17:23:38 +00:00
|
|
|
if is_target_sprite {
|
|
|
|
log::warn!("GetURL into target sprite is not yet implemented");
|
|
|
|
return Ok(()); //maybe error?
|
|
|
|
}
|
|
|
|
|
|
|
|
if is_load_vars {
|
2019-09-03 01:49:36 +00:00
|
|
|
log::warn!("Reading AVM locals from forms is not yet implemented");
|
2019-09-02 17:23:38 +00:00
|
|
|
return Ok(()); //maybe error?
|
|
|
|
}
|
2019-09-17 03:37:11 +00:00
|
|
|
|
2019-09-03 01:49:36 +00:00
|
|
|
let vars = match NavigationMethod::from_send_vars_method(swf_method) {
|
2019-10-08 14:34:08 +00:00
|
|
|
Some(method) => Some((method, self.locals_into_form_values(context))),
|
2019-09-17 03:37:11 +00:00
|
|
|
None => None,
|
2019-09-03 01:49:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
context.navigator.navigate_to_url(url, Some(target), vars);
|
2019-08-26 23:38:37 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-26 07:58:24 +00:00
|
|
|
fn action_goto_frame(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-26 07:58:24 +00:00
|
|
|
frame: u16,
|
|
|
|
) -> Result<(), Error> {
|
2019-12-19 23:42:26 +00:00
|
|
|
if let Some(clip) = self.target_clip() {
|
2019-12-09 22:19:35 +00:00
|
|
|
if let Some(clip) = clip.as_movie_clip() {
|
2019-09-28 01:09:52 +00:00
|
|
|
// The frame on the stack is 0-based, not 1-based.
|
2019-10-27 18:58:30 +00:00
|
|
|
clip.goto_frame(context, frame + 1, true);
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
|
|
|
log::error!("GotoFrame failed: Target is not a MovieClip");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::error!("GotoFrame failed: Invalid target");
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_goto_frame_2(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-08-26 23:38:37 +00:00
|
|
|
set_playing: bool,
|
|
|
|
scene_offset: u16,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Version 4+ gotoAndPlay/gotoAndStop
|
|
|
|
// Param can either be a frame number or a frame label.
|
2019-12-19 23:42:26 +00:00
|
|
|
if let Some(clip) = self.target_clip() {
|
2019-12-09 22:19:35 +00:00
|
|
|
if let Some(clip) = clip.as_movie_clip() {
|
2020-01-07 23:59:14 +00:00
|
|
|
match self.pop() {
|
2019-09-17 09:00:57 +00:00
|
|
|
Value::Number(frame) => {
|
2019-09-28 01:09:52 +00:00
|
|
|
// The frame on the stack is 1-based, not 0-based.
|
2019-10-27 18:58:30 +00:00
|
|
|
clip.goto_frame(context, scene_offset + (frame as u16), !set_playing)
|
2019-09-17 09:00:57 +00:00
|
|
|
}
|
|
|
|
Value::String(frame_label) => {
|
|
|
|
if let Some(frame) = clip.frame_label_to_number(&frame_label) {
|
2019-10-27 18:58:30 +00:00
|
|
|
clip.goto_frame(context, scene_offset + frame, !set_playing)
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
|
|
|
log::warn!(
|
|
|
|
"GotoFrame2: MovieClip {} does not contain frame label '{}'",
|
|
|
|
clip.id(),
|
|
|
|
frame_label
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => log::warn!("GotoFrame2: Expected frame label or number"),
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
|
|
|
log::warn!("GotoFrame2: Target is not a MovieClip");
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
|
|
|
log::warn!("GotoFrame2: Invalid target");
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-26 07:58:24 +00:00
|
|
|
fn action_goto_label(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-26 07:58:24 +00:00
|
|
|
label: &str,
|
|
|
|
) -> Result<(), Error> {
|
2019-12-19 23:42:26 +00:00
|
|
|
if let Some(clip) = self.target_clip() {
|
2019-12-09 22:19:35 +00:00
|
|
|
if let Some(clip) = clip.as_movie_clip() {
|
2019-09-17 09:00:57 +00:00
|
|
|
if let Some(frame) = clip.frame_label_to_number(label) {
|
2019-10-27 18:58:30 +00:00
|
|
|
clip.goto_frame(context, frame, true);
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
|
|
|
log::warn!("GoToLabel: Frame label '{}' not found", label);
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
} else {
|
2019-09-17 09:00:57 +00:00
|
|
|
log::warn!("GoToLabel: Target is not a MovieClip");
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-09-17 09:00:57 +00:00
|
|
|
log::warn!("GoToLabel: Invalid target");
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_if(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
_context: &mut UpdateContext,
|
2019-09-26 00:25:55 +00:00
|
|
|
jump_offset: i16,
|
2019-10-06 21:45:14 +00:00
|
|
|
reader: &mut Reader<'_>,
|
2019-08-26 23:38:37 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let val = self.pop();
|
2019-10-10 12:28:14 +00:00
|
|
|
if val.as_bool(self.current_swf_version()) {
|
2019-09-26 00:25:55 +00:00
|
|
|
reader.seek(jump_offset.into());
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 00:35:22 +00:00
|
|
|
fn action_increment(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_number(self, context)?;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(a + 1.0);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-11 23:30:04 +00:00
|
|
|
fn action_init_array(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let num_elements = self.pop().as_i64()?;
|
2019-12-13 14:50:58 +00:00
|
|
|
let array = ScriptObject::array(context.gc_context, Some(self.prototypes.array));
|
2019-12-11 23:30:04 +00:00
|
|
|
|
|
|
|
for i in 0..num_elements {
|
2020-01-07 23:59:14 +00:00
|
|
|
array.set_array_element(i as usize, self.pop(), context.gc_context);
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-12-11 23:30:04 +00:00
|
|
|
self.push(Value::Object(array.into()));
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-21 02:13:29 +00:00
|
|
|
fn action_init_object(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let num_props = self.pop().as_i64()?;
|
2019-12-06 18:24:36 +00:00
|
|
|
let object = ScriptObject::object(context.gc_context, Some(self.prototypes.object));
|
2019-08-26 23:38:37 +00:00
|
|
|
for _ in 0..num_props {
|
2020-01-07 23:59:14 +00:00
|
|
|
let value = self.pop();
|
|
|
|
let name = self.pop().into_string();
|
2019-12-10 09:33:22 +00:00
|
|
|
object.set(&name, value, self, context)?;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
self.push(Value::Object(object.into()));
|
2019-10-21 02:13:29 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-28 22:30:22 +00:00
|
|
|
fn action_implements_op(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let constr = self.pop().as_object()?;
|
|
|
|
let count = self.pop().as_i64()?; //TODO: Is this coercion actually performed by Flash?
|
2019-10-28 22:30:22 +00:00
|
|
|
let mut interfaces = vec![];
|
|
|
|
|
|
|
|
//TODO: If one of the interfaces is not an object, do we leave the
|
|
|
|
//whole stack dirty, or...?
|
|
|
|
for _ in 0..count {
|
2020-01-07 23:59:14 +00:00
|
|
|
interfaces.push(self.pop().as_object()?);
|
2019-10-28 22:30:22 +00:00
|
|
|
}
|
|
|
|
|
2019-10-29 03:12:16 +00:00
|
|
|
let mut prototype = constr
|
|
|
|
.get("prototype", self, context)?
|
|
|
|
.resolve(self, context)?
|
|
|
|
.as_object()?;
|
|
|
|
|
|
|
|
prototype.set_interfaces(context.gc_context, interfaces);
|
2019-10-28 22:30:22 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-28 18:43:19 +00:00
|
|
|
fn action_instance_of(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let constr = self.pop().as_object()?;
|
|
|
|
let obj = self.pop().as_object()?;
|
2019-10-28 18:43:19 +00:00
|
|
|
|
|
|
|
let prototype = constr
|
2019-12-10 09:33:22 +00:00
|
|
|
.get("prototype", self, context)?
|
2019-10-28 18:43:19 +00:00
|
|
|
.resolve(self, context)?
|
|
|
|
.as_object()?;
|
2019-11-30 05:30:10 +00:00
|
|
|
let is_instance_of = obj.is_instance_of(self, context, constr, prototype)?;
|
2019-10-28 18:43:19 +00:00
|
|
|
|
2019-11-30 05:30:10 +00:00
|
|
|
self.push(is_instance_of);
|
2019-10-28 18:43:19 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
fn action_jump(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
_context: &mut UpdateContext,
|
2019-09-26 00:25:55 +00:00
|
|
|
jump_offset: i16,
|
2019-10-06 21:45:14 +00:00
|
|
|
reader: &mut Reader<'_>,
|
2019-08-26 23:38:37 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Handle out-of-bounds.
|
2019-09-26 00:25:55 +00:00
|
|
|
reader.seek(jump_offset.into());
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_less(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// AS1 less than
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = b.into_number_v1() < a.into_number_v1();
|
2019-12-22 06:40:06 +00:00
|
|
|
self.push(Value::from_bool(result, self.current_swf_version()));
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 00:35:22 +00:00
|
|
|
fn action_less_2(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
|
|
|
// ECMA-262 s. 11.8.1
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2019-11-23 22:35:24 +00:00
|
|
|
let result = b.abstract_lt(a, self, context)?;
|
2019-08-26 23:38:37 +00:00
|
|
|
|
2019-10-21 10:55:17 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 00:35:22 +00:00
|
|
|
fn action_greater(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
|
|
|
// ECMA-262 s. 11.8.2
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-10-27 00:35:22 +00:00
|
|
|
|
2019-11-23 22:35:24 +00:00
|
|
|
let result = a.abstract_lt(b, self, context)?;
|
2019-10-27 00:35:22 +00:00
|
|
|
|
|
|
|
self.push(result);
|
2019-10-08 06:06:05 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_mb_ascii_to_char(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// TODO(Herschel): Results on incorrect operands?
|
|
|
|
use std::convert::TryFrom;
|
2020-01-07 23:59:14 +00:00
|
|
|
let val = char::try_from(self.pop().as_f64()? as u32)?;
|
2019-10-21 10:30:59 +00:00
|
|
|
self.push(val.to_string());
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_mb_char_to_ascii(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// TODO(Herschel): Results on incorrect operands?
|
2020-01-07 23:59:14 +00:00
|
|
|
let s = self.pop().into_string();
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = s.chars().nth(0).unwrap_or('\0') as u32;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_mb_string_extract(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// TODO(Herschel): Result with incorrect operands?
|
2020-01-07 23:59:14 +00:00
|
|
|
let len = self.pop().as_f64()? as usize;
|
|
|
|
let start = self.pop().as_f64()? as usize;
|
|
|
|
let s = self.pop().into_string();
|
2019-08-26 23:38:37 +00:00
|
|
|
let result = s[len..len + start].to_string(); // TODO(Herschel): Flash uses UTF-16 internally.
|
2019-10-21 10:30:59 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_mb_string_length(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// TODO(Herschel): Result with non-string operands?
|
2020-01-07 23:59:14 +00:00
|
|
|
let val = self.pop().into_string().len();
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(val as f64);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-22 11:01:58 +00:00
|
|
|
fn action_multiply(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_number(self, context)?;
|
|
|
|
let b = self.pop().as_number(self, context)?;
|
2019-12-22 11:01:58 +00:00
|
|
|
self.push(a * b);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-22 11:01:58 +00:00
|
|
|
fn action_modulo(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// TODO: Wrong operands?
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_number(self, context)?;
|
|
|
|
let b = self.pop().as_number(self, context)?;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(a % b);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_not(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-12-22 06:40:06 +00:00
|
|
|
let version = self.current_swf_version();
|
2020-01-07 23:59:14 +00:00
|
|
|
let val = !self.pop().as_bool(version);
|
2019-12-22 06:40:06 +00:00
|
|
|
self.push(Value::from_bool(val, version));
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_next_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-12-19 23:42:26 +00:00
|
|
|
if let Some(clip) = self.target_clip() {
|
2019-12-09 22:19:35 +00:00
|
|
|
if let Some(clip) = clip.as_movie_clip() {
|
2019-10-27 18:58:30 +00:00
|
|
|
clip.next_frame(context);
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
|
|
|
log::warn!("NextFrame: Target is not a MovieClip");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::warn!("NextFrame: Invalid target");
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-17 18:11:20 +00:00
|
|
|
fn action_new_method(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let method_name = self.pop();
|
2020-01-22 01:50:15 +00:00
|
|
|
let object_val = self.pop();
|
2020-01-07 23:59:14 +00:00
|
|
|
let num_args = self.pop().as_i64()?;
|
2019-10-17 18:11:20 +00:00
|
|
|
let mut args = Vec::new();
|
|
|
|
for _ in 0..num_args {
|
2020-01-07 23:59:14 +00:00
|
|
|
args.push(self.pop());
|
2019-10-17 18:11:20 +00:00
|
|
|
}
|
|
|
|
|
2020-01-22 01:50:15 +00:00
|
|
|
let object = value_object::ValueObject::boxed(self, context, object_val);
|
2019-10-17 18:11:20 +00:00
|
|
|
let constructor = object
|
2019-12-10 09:33:22 +00:00
|
|
|
.get(&method_name.as_string()?, self, context)?
|
2019-10-19 15:06:33 +00:00
|
|
|
.resolve(self, context)?
|
2019-10-17 18:11:20 +00:00
|
|
|
.as_object()?;
|
2019-10-28 03:54:35 +00:00
|
|
|
let prototype = constructor
|
2019-12-10 09:33:22 +00:00
|
|
|
.get("prototype", self, context)?
|
2019-10-28 03:54:35 +00:00
|
|
|
.resolve(self, context)?
|
|
|
|
.as_object()?;
|
2019-10-25 03:21:35 +00:00
|
|
|
|
2019-12-06 18:24:36 +00:00
|
|
|
let this = prototype.new(self, context, prototype, &args)?;
|
2019-10-17 18:11:20 +00:00
|
|
|
|
|
|
|
//TODO: What happens if you `ActionNewMethod` without a method name?
|
2019-10-19 15:06:33 +00:00
|
|
|
constructor
|
|
|
|
.call(self, context, this, &args)?
|
2019-11-27 19:52:07 +00:00
|
|
|
.resolve(self, context)?;
|
2019-10-17 18:11:20 +00:00
|
|
|
|
2019-11-26 20:07:59 +00:00
|
|
|
self.push(this);
|
2019-10-17 18:11:20 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-17 18:11:20 +00:00
|
|
|
fn action_new_object(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let fn_name = self.pop();
|
|
|
|
let num_args = self.pop().as_i64()?;
|
2019-10-17 18:11:20 +00:00
|
|
|
let mut args = Vec::new();
|
2019-08-26 23:38:37 +00:00
|
|
|
for _ in 0..num_args {
|
2020-01-07 23:59:14 +00:00
|
|
|
args.push(self.pop());
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2019-10-17 18:11:20 +00:00
|
|
|
|
2019-12-18 21:34:19 +00:00
|
|
|
let mut ret = Value::Undefined;
|
2019-10-25 03:21:35 +00:00
|
|
|
|
2019-12-18 21:34:19 +00:00
|
|
|
if let Ok(fn_name) = fn_name.as_string() {
|
|
|
|
if let Ok(constructor) = self
|
|
|
|
.stack_frames
|
|
|
|
.last()
|
|
|
|
.unwrap()
|
|
|
|
.clone()
|
|
|
|
.read()
|
|
|
|
.resolve(fn_name, self, context)?
|
|
|
|
.resolve(self, context)?
|
|
|
|
.as_object()
|
|
|
|
{
|
|
|
|
if let Ok(prototype) = constructor
|
|
|
|
.get("prototype", self, context)?
|
|
|
|
.resolve(self, context)?
|
|
|
|
.as_object()
|
|
|
|
{
|
|
|
|
let this = prototype.new(self, context, prototype, &args)?;
|
|
|
|
constructor
|
|
|
|
.call(self, context, this, &args)?
|
|
|
|
.resolve(self, context)?;
|
|
|
|
ret = this.into();
|
|
|
|
} else {
|
|
|
|
log::warn!("NewObject: Constructor has invalid prototype: {}", fn_name);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::warn!("NewObject: Object is not a function: {}", fn_name);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::warn!("NewObject: Expected String for object name: {:?}", fn_name);
|
|
|
|
}
|
2019-10-17 18:11:20 +00:00
|
|
|
|
2019-12-18 21:34:19 +00:00
|
|
|
self.push(ret);
|
2019-10-17 18:11:20 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_or(&mut self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// AS1 logical or
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-12-22 06:40:06 +00:00
|
|
|
let version = self.current_swf_version();
|
|
|
|
let result = b.as_bool(version) || a.as_bool(version);
|
|
|
|
self.push(Value::from_bool(result, version));
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-07 02:29:36 +00:00
|
|
|
fn action_play(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-12-19 23:42:26 +00:00
|
|
|
if let Some(clip) = self.target_clip() {
|
2019-12-09 22:19:35 +00:00
|
|
|
if let Some(clip) = clip.as_movie_clip() {
|
2019-12-07 02:29:36 +00:00
|
|
|
clip.play(context)
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
|
|
|
log::warn!("Play: Target is not a MovieClip");
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
} else {
|
2019-09-17 09:00:57 +00:00
|
|
|
log::warn!("Play: Invalid target");
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_prev_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2019-12-19 23:42:26 +00:00
|
|
|
if let Some(clip) = self.target_clip() {
|
2019-12-09 22:19:35 +00:00
|
|
|
if let Some(clip) = clip.as_movie_clip() {
|
2019-10-27 18:58:30 +00:00
|
|
|
clip.prev_frame(context);
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
|
|
|
log::warn!("PrevFrame: Target is not a MovieClip");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::warn!("PrevFrame: Invalid target");
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_pop(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
self.pop();
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_push(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
_context: &mut UpdateContext,
|
2019-08-26 23:38:37 +00:00
|
|
|
values: &[swf::avm1::types::Value],
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
for value in values {
|
|
|
|
use swf::avm1::types::Value as SwfValue;
|
|
|
|
let value = match value {
|
|
|
|
SwfValue::Undefined => Value::Undefined,
|
|
|
|
SwfValue::Null => Value::Null,
|
|
|
|
SwfValue::Bool(v) => Value::Bool(*v),
|
2019-11-05 02:47:21 +00:00
|
|
|
SwfValue::Int(v) => f64::from(*v).into(),
|
|
|
|
SwfValue::Float(v) => f64::from(*v).into(),
|
|
|
|
SwfValue::Double(v) => (*v).into(),
|
2019-12-19 17:10:41 +00:00
|
|
|
SwfValue::Str(v) => (*v).to_string().into(),
|
2019-09-27 00:02:40 +00:00
|
|
|
SwfValue::Register(v) => self.current_register(*v),
|
2019-08-26 23:38:37 +00:00
|
|
|
SwfValue::ConstantPool(i) => {
|
2019-11-03 22:43:28 +00:00
|
|
|
if let Some(value) = self
|
|
|
|
.current_stack_frame()
|
|
|
|
.unwrap()
|
|
|
|
.read()
|
|
|
|
.constant_pool()
|
|
|
|
.read()
|
|
|
|
.get(*i as usize)
|
|
|
|
{
|
2019-10-21 10:30:59 +00:00
|
|
|
value.to_string().into()
|
2019-08-26 23:38:37 +00:00
|
|
|
} else {
|
|
|
|
log::warn!(
|
|
|
|
"ActionPush: Constant pool index {} out of range (len = {})",
|
|
|
|
i,
|
2019-11-03 22:43:28 +00:00
|
|
|
self.current_stack_frame()
|
|
|
|
.unwrap()
|
|
|
|
.read()
|
|
|
|
.constant_pool()
|
|
|
|
.read()
|
|
|
|
.len()
|
2019-08-26 23:38:37 +00:00
|
|
|
);
|
|
|
|
Value::Undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
self.push(value);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_push_duplicate(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-09-17 05:02:37 +00:00
|
|
|
let val = self.stack.last().ok_or("Stack underflow")?.clone();
|
2019-08-26 23:38:37 +00:00
|
|
|
self.push(val);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_random_number(&mut self, context: &mut UpdateContext) -> Result<(), Error> {
|
2019-09-28 02:32:22 +00:00
|
|
|
// A max value < 0 will always return 0,
|
|
|
|
// and the max value gets converted into an i32, so any number > 2^31 - 1 will return 0.
|
2020-01-07 23:59:14 +00:00
|
|
|
let max = self.pop().into_number_v1() as i32;
|
2020-01-14 08:05:13 +00:00
|
|
|
let val = if max > 0 {
|
|
|
|
context.rng.gen_range(0, max)
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(val);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-17 09:51:00 +00:00
|
|
|
fn action_remove_sprite(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
2020-01-23 02:14:23 +00:00
|
|
|
let target = self.pop();
|
|
|
|
let target_clip = self.resolve_target_display_object(context, target)?;
|
2019-12-17 09:51:00 +00:00
|
|
|
|
|
|
|
if let Some(target_clip) = target_clip.and_then(|o| o.as_movie_clip()) {
|
|
|
|
let _ = globals::movie_clip::remove_movie_clip(target_clip, context, 0);
|
|
|
|
} else {
|
|
|
|
log::warn!("RemoveSprite: Source is not a movie clip");
|
|
|
|
}
|
2019-09-28 00:28:14 +00:00
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-03 02:07:00 +00:00
|
|
|
fn action_return(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let return_value = self.pop();
|
2019-10-18 19:10:26 +00:00
|
|
|
self.retire_stack_frame(context, return_value)?;
|
2019-09-16 23:15:22 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_set_member(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let value = self.pop();
|
|
|
|
let name_val = self.pop();
|
2019-10-27 00:35:22 +00:00
|
|
|
let name = name_val.coerce_to_string(self, context)?;
|
2019-09-26 00:06:44 +00:00
|
|
|
|
2020-01-07 23:59:14 +00:00
|
|
|
let object = self.pop();
|
2019-12-13 21:36:21 +00:00
|
|
|
if let Ok(object) = object.as_object() {
|
|
|
|
object.set(&name, value, self, context)?;
|
|
|
|
} else {
|
|
|
|
log::warn!(
|
|
|
|
"Attempted to set member {} of {:?} to {:?}",
|
|
|
|
name,
|
|
|
|
object,
|
|
|
|
value
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-09-26 00:06:44 +00:00
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-12-04 01:52:00 +00:00
|
|
|
fn action_set_property(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let value = self.pop();
|
|
|
|
let prop_index = self.pop().as_u32()? as usize;
|
2020-01-23 02:14:23 +00:00
|
|
|
let path = self.pop();
|
|
|
|
if self.target_clip().is_some() {
|
|
|
|
if let Some(clip) = self.resolve_target_display_object(context, path)? {
|
2019-12-04 01:52:00 +00:00
|
|
|
let display_properties = self.display_properties;
|
|
|
|
let props = display_properties.read();
|
|
|
|
if let Some(property) = props.get_by_index(prop_index) {
|
|
|
|
property.set(self, context, clip, value)?;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
2020-01-23 02:14:23 +00:00
|
|
|
log::warn!("SetProperty: Invalid target");
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-09-17 09:00:57 +00:00
|
|
|
log::warn!("SetProperty: Invalid base clip");
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-26 22:53:50 +00:00
|
|
|
fn action_set_variable(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-08-26 22:53:50 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// Flash 4-style variable
|
2020-01-07 23:59:14 +00:00
|
|
|
let value = self.pop();
|
2020-01-23 02:14:23 +00:00
|
|
|
let var_path = self.pop().coerce_to_string(self, context)?;
|
|
|
|
self.set_variable(context, &var_path, value)
|
2019-08-26 22:53:50 +00:00
|
|
|
}
|
|
|
|
|
2019-10-08 20:04:26 +00:00
|
|
|
#[allow(clippy::float_cmp)]
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_strict_equals(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-10-08 06:06:05 +00:00
|
|
|
// The same as normal equality but types must match
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-10-08 20:40:15 +00:00
|
|
|
let result = a == b;
|
2019-10-21 10:55:17 +00:00
|
|
|
self.push(result);
|
2019-10-08 06:06:05 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
fn action_set_target(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-08-26 23:38:37 +00:00
|
|
|
target: &str,
|
|
|
|
) -> Result<(), Error> {
|
2019-12-19 23:42:26 +00:00
|
|
|
let stack_frame = self.current_stack_frame().unwrap();
|
|
|
|
let mut sf = stack_frame.write(context.gc_context);
|
|
|
|
let base_clip = sf.base_clip();
|
2019-08-26 23:38:37 +00:00
|
|
|
if target.is_empty() {
|
2019-12-19 23:42:26 +00:00
|
|
|
sf.set_target_clip(Some(base_clip));
|
2020-01-23 02:14:23 +00:00
|
|
|
} else if let Some(clip) = self
|
|
|
|
.resolve_target_path(context, base_clip, target)?
|
|
|
|
.and_then(|o| o.as_display_object())
|
|
|
|
{
|
2019-12-19 23:42:26 +00:00
|
|
|
sf.set_target_clip(Some(clip));
|
2019-08-26 23:38:37 +00:00
|
|
|
} else {
|
|
|
|
log::warn!("SetTarget failed: {} not found", target);
|
2019-09-17 09:00:57 +00:00
|
|
|
// TODO: Emulate AVM1 trace error message.
|
|
|
|
// log::info!(target: "avm_trace", "Target not found: Target=\"{}\" Base=\"{}\"", target, context.root.read().name());
|
|
|
|
|
|
|
|
// When SetTarget has an invalid target, subsequent GetVariables act
|
|
|
|
// as if they are targeting root, but subsequent Play/Stop/etc.
|
|
|
|
// fail silenty.
|
2019-12-19 23:42:26 +00:00
|
|
|
sf.set_target_clip(None);
|
2019-09-17 17:28:10 +00:00
|
|
|
}
|
2019-10-04 23:27:08 +00:00
|
|
|
|
2019-12-19 23:42:26 +00:00
|
|
|
let scope = sf.scope_cell();
|
|
|
|
let clip_obj = sf
|
|
|
|
.target_clip()
|
|
|
|
.unwrap_or(context.root)
|
|
|
|
.object()
|
|
|
|
.as_object()
|
|
|
|
.unwrap();
|
2019-10-04 23:27:08 +00:00
|
|
|
|
2019-12-19 23:42:26 +00:00
|
|
|
sf.set_scope(Scope::new_target_scope(scope, clip_obj, context.gc_context));
|
2019-09-17 17:28:10 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-06 21:45:14 +00:00
|
|
|
fn action_set_target2(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let target = self.pop();
|
2019-12-18 01:40:17 +00:00
|
|
|
match target {
|
|
|
|
Value::String(target) => {
|
|
|
|
return self.action_set_target(context, &target);
|
|
|
|
}
|
|
|
|
Value::Undefined => {
|
|
|
|
// Reset
|
2019-12-19 23:42:26 +00:00
|
|
|
let stack_frame = self.current_stack_frame().unwrap();
|
|
|
|
let mut sf = stack_frame.write(context.gc_context);
|
|
|
|
let base_clip = sf.base_clip();
|
|
|
|
sf.set_target_clip(Some(base_clip));
|
2019-12-18 01:40:17 +00:00
|
|
|
}
|
|
|
|
Value::Object(o) => {
|
|
|
|
if let Some(clip) = o.as_display_object() {
|
2019-12-19 23:42:26 +00:00
|
|
|
let stack_frame = self.current_stack_frame().unwrap();
|
|
|
|
let mut sf = stack_frame.write(context.gc_context);
|
2019-12-18 01:40:17 +00:00
|
|
|
// Movieclips can be targetted directly
|
2019-12-19 23:42:26 +00:00
|
|
|
sf.set_target_clip(Some(clip));
|
2019-12-18 01:40:17 +00:00
|
|
|
} else {
|
|
|
|
// Other objects get coerced to string
|
|
|
|
let target = target.coerce_to_string(self, context)?;
|
2020-01-23 02:14:23 +00:00
|
|
|
return self.action_set_target(context, &target);
|
2019-12-18 01:40:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
let target = target.coerce_to_string(self, context)?;
|
2020-01-23 02:14:23 +00:00
|
|
|
return self.action_set_target(context, &target);
|
2019-12-18 01:40:17 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-12-19 23:42:26 +00:00
|
|
|
let stack_frame = self.current_stack_frame().unwrap();
|
|
|
|
let mut sf = stack_frame.write(context.gc_context);
|
|
|
|
let scope = sf.scope_cell();
|
|
|
|
let clip_obj = sf
|
|
|
|
.target_clip()
|
|
|
|
.unwrap_or(context.root)
|
|
|
|
.object()
|
|
|
|
.as_object()
|
|
|
|
.unwrap();
|
|
|
|
sf.set_scope(Scope::new_target_scope(scope, clip_obj, context.gc_context));
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_stack_swap(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-08-26 23:38:37 +00:00
|
|
|
self.push(a);
|
|
|
|
self.push(b);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-21 23:37:27 +00:00
|
|
|
fn action_start_drag(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let target = self.pop();
|
2020-01-23 02:14:23 +00:00
|
|
|
let display_object = self.resolve_target_display_object(context, target)?;
|
2019-12-21 23:37:27 +00:00
|
|
|
if let Some(display_object) = display_object {
|
2020-01-07 23:59:14 +00:00
|
|
|
let lock_center = self.pop();
|
|
|
|
let constrain = self.pop().as_bool(self.current_swf_version());
|
2019-12-21 23:37:27 +00:00
|
|
|
if constrain {
|
2020-01-07 23:59:14 +00:00
|
|
|
let y2 = self.pop();
|
|
|
|
let x2 = self.pop();
|
|
|
|
let y1 = self.pop();
|
|
|
|
let x1 = self.pop();
|
2019-12-21 23:37:27 +00:00
|
|
|
start_drag(
|
|
|
|
display_object,
|
|
|
|
self,
|
|
|
|
context,
|
|
|
|
&[lock_center, x1, y1, x2, y2],
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
start_drag(display_object, self, context, &[lock_center]);
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
log::warn!("StartDrag: Invalid target");
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-19 23:42:26 +00:00
|
|
|
fn action_stop(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
|
|
|
if let Some(clip) = self.target_clip() {
|
2019-12-09 22:19:35 +00:00
|
|
|
if let Some(clip) = clip.as_movie_clip() {
|
2019-10-29 01:12:44 +00:00
|
|
|
clip.stop(context);
|
2019-09-17 09:00:57 +00:00
|
|
|
} else {
|
|
|
|
log::warn!("Stop: Target is not a MovieClip");
|
|
|
|
}
|
2019-08-26 23:38:37 +00:00
|
|
|
} else {
|
2019-09-17 09:00:57 +00:00
|
|
|
log::warn!("Stop: Invalid target");
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_stop_sounds(&mut self, context: &mut UpdateContext) -> Result<(), Error> {
|
2019-09-19 07:21:22 +00:00
|
|
|
context.audio.stop_all_sounds();
|
2019-09-17 22:48:22 +00:00
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_store_register(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-09-25 22:59:02 +00:00
|
|
|
register: u8,
|
2019-08-26 23:38:37 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Does NOT pop the value from the stack.
|
2019-09-25 22:59:02 +00:00
|
|
|
let val = self.stack.last().ok_or("Stack underflow")?.clone();
|
2019-09-27 00:02:40 +00:00
|
|
|
self.set_current_register(register, val, context);
|
2019-09-25 22:59:02 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_string_add(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// SWFv4 string concatenation
|
|
|
|
// TODO(Herschel): Result with non-string operands?
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().into_string();
|
|
|
|
let mut b = self.pop().into_string();
|
2019-08-26 23:38:37 +00:00
|
|
|
b.push_str(&a);
|
2019-10-21 10:30:59 +00:00
|
|
|
self.push(b);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-11 22:30:59 +00:00
|
|
|
fn action_string_equals(
|
|
|
|
&mut self,
|
2019-10-27 00:35:22 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-11 22:30:59 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// AS1 strcmp
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-10-27 00:35:22 +00:00
|
|
|
let result = b.coerce_to_string(self, context)? == a.coerce_to_string(self, context)?;
|
2019-12-22 06:40:06 +00:00
|
|
|
self.push(Value::from_bool(result, self.current_swf_version()));
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_string_extract(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// SWFv4 substring
|
|
|
|
// TODO(Herschel): Result with incorrect operands?
|
2020-01-07 23:59:14 +00:00
|
|
|
let len = self.pop().as_f64()? as usize;
|
|
|
|
let start = self.pop().as_f64()? as usize;
|
|
|
|
let s = self.pop().into_string();
|
2019-08-26 23:38:37 +00:00
|
|
|
// This is specifically a non-UTF8 aware substring.
|
|
|
|
// SWFv4 only used ANSI strings.
|
|
|
|
let result = s
|
|
|
|
.bytes()
|
|
|
|
.skip(start)
|
|
|
|
.take(len)
|
|
|
|
.map(|c| c as char)
|
|
|
|
.collect::<String>();
|
2019-10-21 10:30:59 +00:00
|
|
|
self.push(result);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-11 22:30:59 +00:00
|
|
|
fn action_string_greater(
|
|
|
|
&mut self,
|
2019-10-27 00:35:22 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-11 22:30:59 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-10-08 06:06:05 +00:00
|
|
|
// AS1 strcmp
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-10-08 06:06:05 +00:00
|
|
|
// This is specifically a non-UTF8 aware comparison.
|
2019-10-27 00:35:22 +00:00
|
|
|
let result = b
|
|
|
|
.coerce_to_string(self, context)?
|
|
|
|
.bytes()
|
|
|
|
.gt(a.coerce_to_string(self, context)?.bytes());
|
2019-12-22 06:40:06 +00:00
|
|
|
self.push(Value::from_bool(result, self.current_swf_version()));
|
2019-10-08 06:06:05 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_string_length(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// AS1 strlen
|
|
|
|
// Only returns byte length.
|
|
|
|
// TODO(Herschel): Result with non-string operands?
|
2020-01-07 23:59:14 +00:00
|
|
|
let val = self.pop().into_string().bytes().len() as f64;
|
2019-10-21 14:10:10 +00:00
|
|
|
self.push(val);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-11 22:30:59 +00:00
|
|
|
fn action_string_less(
|
|
|
|
&mut self,
|
2019-10-27 00:35:22 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-11 22:30:59 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// AS1 strcmp
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop();
|
|
|
|
let b = self.pop();
|
2019-08-26 23:38:37 +00:00
|
|
|
// This is specifically a non-UTF8 aware comparison.
|
2019-10-27 00:35:22 +00:00
|
|
|
let result = b
|
|
|
|
.coerce_to_string(self, context)?
|
|
|
|
.bytes()
|
|
|
|
.lt(a.coerce_to_string(self, context)?.bytes());
|
2019-12-22 06:40:06 +00:00
|
|
|
self.push(Value::from_bool(result, self.current_swf_version()));
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-22 11:01:58 +00:00
|
|
|
fn action_subtract(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let a = self.pop().as_number(self, context)?;
|
|
|
|
let b = self.pop().as_number(self, context)?;
|
2019-12-22 11:01:58 +00:00
|
|
|
self.push(b - a);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-26 22:53:50 +00:00
|
|
|
fn action_target_path(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-08-26 22:53:50 +00:00
|
|
|
) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// TODO(Herschel)
|
2020-01-07 23:59:14 +00:00
|
|
|
let _clip = self.pop().as_object()?;
|
2019-08-26 23:38:37 +00:00
|
|
|
self.push(Value::Undefined);
|
|
|
|
Err("Unimplemented action: TargetPath".into())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn toggle_quality(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2019-08-26 23:38:37 +00:00
|
|
|
// TODO(Herschel): Noop for now? Could chang anti-aliasing on render backend.
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-12-22 11:01:58 +00:00
|
|
|
fn action_to_integer(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let val = self.pop().as_number(self, context)?;
|
2019-12-22 11:01:58 +00:00
|
|
|
self.push(val.trunc());
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 00:35:22 +00:00
|
|
|
fn action_to_number(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let val = self.pop().as_number(self, context)?;
|
2019-10-27 00:35:22 +00:00
|
|
|
self.push(val);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 00:35:22 +00:00
|
|
|
fn action_to_string(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let val = self.pop().coerce_to_string(self, context)?;
|
2019-10-27 00:35:22 +00:00
|
|
|
self.push(val);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 00:35:22 +00:00
|
|
|
fn action_trace(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let val = self.pop().coerce_to_string(self, context)?;
|
2019-10-27 00:35:22 +00:00
|
|
|
log::info!(target: "avm_trace", "{}", val);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-27 18:58:30 +00:00
|
|
|
fn action_type_of(&mut self, _context: &mut UpdateContext) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let type_of = self.pop().type_of();
|
2019-08-31 12:09:37 +00:00
|
|
|
self.push(type_of);
|
2019-08-26 23:38:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_wait_for_frame(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
_context: &mut UpdateContext,
|
2019-08-26 23:38:37 +00:00
|
|
|
_frame: u16,
|
2019-09-29 13:12:31 +00:00
|
|
|
num_actions_to_skip: u8,
|
2019-10-06 21:45:14 +00:00
|
|
|
r: &mut Reader<'_>,
|
2019-08-26 23:38:37 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Always true for now.
|
|
|
|
let loaded = true;
|
|
|
|
if !loaded {
|
|
|
|
// Note that the offset is given in # of actions, NOT in bytes.
|
|
|
|
// Read the actions and toss them away.
|
2019-09-29 13:12:31 +00:00
|
|
|
skip_actions(r, num_actions_to_skip)?;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_wait_for_frame_2(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
_context: &mut UpdateContext,
|
2019-09-29 13:12:31 +00:00
|
|
|
num_actions_to_skip: u8,
|
2019-10-06 21:45:14 +00:00
|
|
|
r: &mut Reader<'_>,
|
2019-08-26 23:38:37 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Always true for now.
|
2020-01-07 23:59:14 +00:00
|
|
|
let _frame_num = self.pop().as_f64()? as u16;
|
2019-08-26 23:38:37 +00:00
|
|
|
let loaded = true;
|
|
|
|
if !loaded {
|
|
|
|
// Note that the offset is given in # of actions, NOT in bytes.
|
|
|
|
// Read the actions and toss them away.
|
2019-09-29 13:12:31 +00:00
|
|
|
skip_actions(r, num_actions_to_skip)?;
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-06 21:45:14 +00:00
|
|
|
fn action_with(
|
|
|
|
&mut self,
|
2019-10-27 18:58:30 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2019-10-06 21:45:14 +00:00
|
|
|
actions: &[u8],
|
|
|
|
) -> Result<(), Error> {
|
2020-01-07 23:59:14 +00:00
|
|
|
let object = self.pop().as_object()?;
|
2019-10-06 21:45:14 +00:00
|
|
|
let block = self
|
|
|
|
.current_stack_frame()
|
|
|
|
.unwrap()
|
2019-10-10 02:58:53 +00:00
|
|
|
.read()
|
2019-10-06 21:45:14 +00:00
|
|
|
.data()
|
|
|
|
.to_subslice(actions)
|
|
|
|
.unwrap();
|
|
|
|
let with_scope = Scope::new_with_scope(
|
2019-10-10 02:58:53 +00:00
|
|
|
self.current_stack_frame().unwrap().read().scope_cell(),
|
2019-10-06 21:45:14 +00:00
|
|
|
object,
|
|
|
|
context.gc_context,
|
|
|
|
);
|
|
|
|
let new_activation = self
|
|
|
|
.current_stack_frame()
|
|
|
|
.unwrap()
|
2019-10-10 02:58:53 +00:00
|
|
|
.read()
|
2019-10-06 21:45:14 +00:00
|
|
|
.to_rescope(block, with_scope);
|
2019-10-10 02:58:53 +00:00
|
|
|
self.stack_frames
|
|
|
|
.push(GcCell::allocate(context.gc_context, new_activation));
|
2019-09-23 01:10:49 +00:00
|
|
|
Ok(())
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-15 21:57:35 +00:00
|
|
|
|
|
|
|
/// Utility function used by `Avm1::action_wait_for_frame` and
|
|
|
|
/// `Avm1::action_wait_for_frame_2`.
|
|
|
|
fn skip_actions(reader: &mut Reader<'_>, num_actions_to_skip: u8) -> Result<(), Error> {
|
|
|
|
for _ in 0..num_actions_to_skip {
|
|
|
|
reader.read_action()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2019-10-06 21:45:14 +00:00
|
|
|
}
|
2019-12-21 23:37:27 +00:00
|
|
|
|
|
|
|
/// Starts draggining this display object, making it follow the cursor.
|
|
|
|
/// Runs via the `startDrag` method or `StartDrag` AVM1 action.
|
|
|
|
pub fn start_drag<'gc>(
|
|
|
|
display_object: DisplayObject<'gc>,
|
|
|
|
avm: &mut Avm1<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
args: &[Value<'gc>],
|
|
|
|
) {
|
|
|
|
let lock_center = args
|
|
|
|
.get(0)
|
|
|
|
.map(|o| o.as_bool(context.swf_version))
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
|
|
let offset = if lock_center {
|
|
|
|
// The object's origin point is locked to the mouse.
|
|
|
|
Default::default()
|
|
|
|
} else {
|
|
|
|
// The object moves relative to current mouse position.
|
|
|
|
// Calculate the offset from the mouse to the object in world space.
|
|
|
|
let obj_pos = display_object.local_to_global(Default::default());
|
|
|
|
(
|
|
|
|
obj_pos.0 - context.mouse_position.0,
|
|
|
|
obj_pos.1 - context.mouse_position.1,
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
let constraint = if args.len() > 1 {
|
|
|
|
// Invalid values turn into 0.
|
|
|
|
let mut x_min = args
|
|
|
|
.get(1)
|
|
|
|
.unwrap_or(&Value::Undefined)
|
|
|
|
.as_number(avm, context)
|
|
|
|
.map(|n| if n.is_finite() { n } else { 0.0 })
|
|
|
|
.map(Twips::from_pixels)
|
|
|
|
.unwrap_or_default();
|
|
|
|
let mut y_min = args
|
|
|
|
.get(2)
|
|
|
|
.unwrap_or(&Value::Undefined)
|
|
|
|
.as_number(avm, context)
|
|
|
|
.map(|n| if n.is_finite() { n } else { 0.0 })
|
|
|
|
.map(Twips::from_pixels)
|
|
|
|
.unwrap_or_default();
|
|
|
|
let mut x_max = args
|
|
|
|
.get(3)
|
|
|
|
.unwrap_or(&Value::Undefined)
|
|
|
|
.as_number(avm, context)
|
|
|
|
.map(|n| if n.is_finite() { n } else { 0.0 })
|
|
|
|
.map(Twips::from_pixels)
|
|
|
|
.unwrap_or_default();
|
|
|
|
let mut y_max = args
|
|
|
|
.get(4)
|
|
|
|
.unwrap_or(&Value::Undefined)
|
|
|
|
.as_number(avm, context)
|
|
|
|
.map(|n| if n.is_finite() { n } else { 0.0 })
|
|
|
|
.map(Twips::from_pixels)
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
// Normalize the bounds.
|
|
|
|
if x_max.get() < x_min.get() {
|
|
|
|
std::mem::swap(&mut x_min, &mut x_max);
|
|
|
|
}
|
|
|
|
if y_max.get() < y_min.get() {
|
|
|
|
std::mem::swap(&mut y_min, &mut y_max);
|
|
|
|
}
|
|
|
|
BoundingBox {
|
|
|
|
valid: true,
|
|
|
|
x_min,
|
|
|
|
y_min,
|
|
|
|
x_max,
|
|
|
|
y_max,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// No constraints.
|
|
|
|
Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let drag_object = crate::player::DragObject {
|
|
|
|
display_object,
|
|
|
|
offset,
|
|
|
|
constraint,
|
|
|
|
};
|
|
|
|
*context.drag_object = Some(drag_object);
|
|
|
|
}
|