2019-09-02 18:45:19 +00:00
|
|
|
use crate::avm1::globals::create_globals;
|
2020-04-13 01:56:45 +00:00
|
|
|
use crate::avm1::object::search_prototype;
|
2020-01-23 02:14:23 +00:00
|
|
|
use crate::avm1::return_value::ReturnValue;
|
2020-01-12 22:06:27 +00:00
|
|
|
use crate::backend::navigator::{NavigationMethod, RequestOptions};
|
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-08-26 23:38:37 +00:00
|
|
|
use std::collections::HashMap;
|
2020-01-12 22:06:27 +00:00
|
|
|
use url::form_urlencoded;
|
2019-08-28 23:29:43 +00:00
|
|
|
|
2019-08-26 23:38:37 +00:00
|
|
|
use swf::avm1::read::Reader;
|
2019-09-15 21:57:35 +00:00
|
|
|
|
2020-01-10 23:28:49 +00:00
|
|
|
use crate::display_object::{DisplayObject, MovieClip};
|
|
|
|
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;
|
2020-05-08 05:58:12 +00:00
|
|
|
pub mod debug;
|
2020-06-20 12:27:49 +00:00
|
|
|
pub mod error;
|
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;
|
2020-06-15 18:42:27 +00:00
|
|
|
pub mod shared_object;
|
2019-12-22 23:32:32 +00:00
|
|
|
mod sound_object;
|
2020-06-25 21:51:42 +00:00
|
|
|
mod stack_frame;
|
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
|
|
|
|
2020-06-20 20:25:21 +00:00
|
|
|
use crate::avm1::error::Error;
|
2019-12-18 16:45:20 +00:00
|
|
|
use crate::avm1::listeners::SystemListener;
|
2020-06-25 21:51:42 +00:00
|
|
|
use crate::avm1::stack_frame::StackFrame;
|
2020-04-12 18:50:34 +00:00
|
|
|
pub 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;
|
2020-06-19 16:32:22 +00:00
|
|
|
use smallvec::alloc::borrow::Cow;
|
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],
|
2020-06-20 21:03:04 +00:00
|
|
|
|
|
|
|
/// If a serious error has occured, or a user has requested it, the AVM may be halted.
|
|
|
|
/// This will completely prevent any further actions from being executed.
|
|
|
|
halted: bool,
|
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
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
],
|
2020-06-20 21:03:04 +00:00
|
|
|
halted: false,
|
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.
|
2020-01-30 04:21:06 +00:00
|
|
|
///
|
|
|
|
/// The `root` is determined relative to the base clip that defined the
|
|
|
|
pub fn target_clip_or_root(&self) -> DisplayObject<'gc> {
|
2019-12-19 23:42:26 +00:00
|
|
|
self.current_stack_frame()
|
|
|
|
.unwrap()
|
|
|
|
.read()
|
|
|
|
.target_clip()
|
2020-01-30 04:21:06 +00:00
|
|
|
.unwrap_or_else(|| self.base_clip().root())
|
2019-12-19 23:42:26 +00:00
|
|
|
}
|
|
|
|
|
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();
|
2020-03-27 05:24:59 +00:00
|
|
|
let keys = locals.get_keys(self);
|
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()
|
2020-05-31 19:07:13 +00:00
|
|
|
.unwrap_or_else(|| Value::Undefined)
|
2020-06-19 17:08:24 +00:00
|
|
|
.coerce_to_string(self, context)
|
|
|
|
.unwrap_or_else(|_| Cow::Borrowed("undefined"))
|
|
|
|
.to_string(),
|
2019-10-26 03:21:14 +00:00
|
|
|
);
|
2019-09-03 01:49:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
form_values
|
|
|
|
}
|
|
|
|
|
2020-01-12 22:06:27 +00:00
|
|
|
/// Construct request options for a fetch operation that may send locals as
|
|
|
|
/// form data in the request body or URL.
|
2020-06-19 16:32:22 +00:00
|
|
|
pub fn locals_into_request_options<'a>(
|
2020-01-12 22:06:27 +00:00
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-06-19 16:32:22 +00:00
|
|
|
url: Cow<'a, str>,
|
2020-01-12 22:06:27 +00:00
|
|
|
method: Option<NavigationMethod>,
|
2020-06-19 16:32:22 +00:00
|
|
|
) -> (Cow<'a, str>, RequestOptions) {
|
2020-01-12 22:06:27 +00:00
|
|
|
match method {
|
|
|
|
Some(method) => {
|
|
|
|
let vars = self.locals_into_form_values(context);
|
|
|
|
let qstring = form_urlencoded::Serializer::new(String::new())
|
|
|
|
.extend_pairs(vars.iter())
|
|
|
|
.finish();
|
|
|
|
|
|
|
|
match method {
|
2020-06-19 16:32:22 +00:00
|
|
|
NavigationMethod::GET if url.find('?').is_none() => (
|
|
|
|
Cow::Owned(format!("{}?{}", url, qstring)),
|
|
|
|
RequestOptions::get(),
|
|
|
|
),
|
|
|
|
NavigationMethod::GET => (
|
|
|
|
Cow::Owned(format!("{}&{}", url, qstring)),
|
|
|
|
RequestOptions::get(),
|
|
|
|
),
|
2020-01-12 22:06:27 +00:00
|
|
|
NavigationMethod::POST => (
|
|
|
|
url,
|
|
|
|
RequestOptions::post(Some((
|
|
|
|
qstring.as_bytes().to_owned(),
|
|
|
|
"application/x-www-form-urlencoded".to_string(),
|
|
|
|
))),
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => (url, RequestOptions::get()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
) {
|
2020-06-20 21:03:04 +00:00
|
|
|
if self.halted {
|
|
|
|
// We've been told to ignore all future execution.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-10-06 21:45:14 +00:00
|
|
|
let global_scope = GcCell::allocate(
|
|
|
|
action_context.gc_context,
|
|
|
|
Scope::from_global_object(self.globals),
|
|
|
|
);
|
2020-06-18 22:16:56 +00:00
|
|
|
let clip_obj = active_clip.object().coerce_to_object(self, action_context);
|
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
|
|
|
}
|
|
|
|
|
2020-06-23 04:07:27 +00:00
|
|
|
/// Add a stack frame that executes code in timeline scope
|
|
|
|
pub fn insert_stack_frame_for_display_object(
|
|
|
|
&mut self,
|
|
|
|
active_clip: DisplayObject<'gc>,
|
|
|
|
swf_version: u8,
|
|
|
|
action_context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) {
|
|
|
|
use crate::tag_utils::SwfMovie;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
let clip_obj = match active_clip.object() {
|
|
|
|
Value::Object(o) => o,
|
|
|
|
_ => panic!("No script object for display object"),
|
|
|
|
};
|
|
|
|
let global_scope = GcCell::allocate(
|
|
|
|
action_context.gc_context,
|
|
|
|
Scope::from_global_object(self.globals),
|
|
|
|
);
|
|
|
|
let child_scope = GcCell::allocate(
|
|
|
|
action_context.gc_context,
|
|
|
|
Scope::new(global_scope, scope::ScopeClass::Target, clip_obj),
|
|
|
|
);
|
|
|
|
self.stack_frames.push(GcCell::allocate(
|
|
|
|
action_context.gc_context,
|
|
|
|
Activation::from_action(
|
|
|
|
swf_version,
|
|
|
|
SwfSlice {
|
|
|
|
movie: Arc::new(SwfMovie::empty(swf_version)),
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
},
|
|
|
|
child_scope,
|
|
|
|
self.constant_pool,
|
|
|
|
active_clip,
|
|
|
|
clip_obj,
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
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, '_>,
|
|
|
|
) {
|
2020-06-20 21:03:04 +00:00
|
|
|
if self.halted {
|
|
|
|
// We've been told to ignore all future execution.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-10-29 04:07:47 +00:00
|
|
|
let global_scope = GcCell::allocate(
|
|
|
|
action_context.gc_context,
|
|
|
|
Scope::from_global_object(self.globals),
|
|
|
|
);
|
2020-06-18 22:16:56 +00:00
|
|
|
let clip_obj = active_clip.object().coerce_to_object(self, action_context);
|
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
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2020-01-16 00:25:50 +00:00
|
|
|
/// Add a stack frame that executes code in timeline scope for an object
|
|
|
|
/// method, such as an event handler.
|
|
|
|
pub fn insert_stack_frame_for_method(
|
2019-12-16 02:10:28 +00:00
|
|
|
&mut self,
|
|
|
|
active_clip: DisplayObject<'gc>,
|
2020-01-16 00:25:50 +00:00
|
|
|
obj: Object<'gc>,
|
2019-12-16 02:10:28 +00:00
|
|
|
swf_version: u8,
|
2019-12-18 19:30:21 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
name: &str,
|
2020-01-16 00:25:50 +00:00
|
|
|
args: &[Value<'gc>],
|
2019-12-16 02:10:28 +00:00
|
|
|
) {
|
2020-06-20 21:03:04 +00:00
|
|
|
if self.halted {
|
|
|
|
// We've been told to ignore all future execution.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-12-18 19:30:21 +00:00
|
|
|
// Grab the property with the given name.
|
|
|
|
// Requires a dummy stack frame.
|
2020-01-16 00:25:50 +00:00
|
|
|
self.stack_frames.push(GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
Activation::from_nothing(swf_version, self.globals, context.gc_context, active_clip),
|
|
|
|
));
|
2020-04-13 01:56:45 +00:00
|
|
|
let search_result = search_prototype(Some(obj), name, self, context, obj)
|
|
|
|
.and_then(|r| Ok((r.0.resolve(self, context)?, r.1)));
|
2020-01-16 00:25:50 +00:00
|
|
|
self.stack_frames.pop();
|
2019-12-18 19:30:21 +00:00
|
|
|
|
2020-01-16 00:25:50 +00:00
|
|
|
// Run the callback.
|
|
|
|
// The function exec pushes its own stack frame.
|
|
|
|
// The function is now ready to execute with `run_stack_till_empty`.
|
2020-04-13 01:56:45 +00:00
|
|
|
if let Ok((callback, base_proto)) = search_result {
|
|
|
|
let _ = callback.call(self, context, obj, base_proto, args);
|
2019-12-18 19:30:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-04-12 18:50:34 +00:00
|
|
|
/// Checks if there is currently a stack frame.
|
|
|
|
///
|
|
|
|
/// This is an indicator if you are currently running from inside or outside the AVM.
|
|
|
|
/// This method is cheaper than `current_stack_frame` as it doesn't need to perform a copy.
|
|
|
|
pub fn has_stack_frame(&self) -> bool {
|
|
|
|
!self.stack_frames.is_empty()
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2020-03-27 05:24:59 +00:00
|
|
|
/// Returns whether property keys should be case sensitive based on the current SWF version.
|
|
|
|
pub fn is_case_sensitive(&self) -> bool {
|
|
|
|
is_swf_case_sensitive(self.current_swf_version())
|
|
|
|
}
|
|
|
|
|
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(..) {
|
2020-04-13 01:56:45 +00:00
|
|
|
let _ = handler.call(self, context, listener, None, &args);
|
2019-12-18 16:45:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 07:40:51 +00:00
|
|
|
/// Execute the AVM stack until a given activation returns.
|
|
|
|
pub fn run_activation(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
activation: GcCell<'gc, Activation<'gc>>,
|
|
|
|
) -> Result<(), Error<'gc>> {
|
|
|
|
match StackFrame::new(self, activation).run(context) {
|
|
|
|
Ok(return_type) => {
|
|
|
|
self.stack_frames.pop();
|
|
|
|
|
|
|
|
let can_return = activation.read().can_return() && !self.stack_frames.is_empty();
|
|
|
|
if can_return {
|
|
|
|
let return_value = return_type.value();
|
|
|
|
activation
|
|
|
|
.write(context.gc_context)
|
|
|
|
.set_return_value(return_value.clone());
|
|
|
|
|
|
|
|
self.push(return_value);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Err(error) => {
|
|
|
|
self.stack_frames.pop();
|
2020-06-26 10:44:25 +00:00
|
|
|
if self.stack_frames.is_empty() {
|
|
|
|
if let Error::ThrownValue(error) = &error {
|
|
|
|
let string = error
|
|
|
|
.coerce_to_string(self, context)
|
|
|
|
.unwrap_or_else(|_| Cow::Borrowed("undefined"));
|
|
|
|
log::info!(target: "avm_trace", "{}", string);
|
|
|
|
}
|
|
|
|
}
|
2020-06-26 07:40:51 +00:00
|
|
|
if error.is_halting() {
|
|
|
|
self.halt();
|
|
|
|
}
|
|
|
|
Err(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-20 21:03:04 +00:00
|
|
|
/// Halts the AVM, preventing execution of any further actions.
|
|
|
|
///
|
|
|
|
/// If the AVM is currently evaluating an action, it will continue until it realizes that it has
|
|
|
|
/// been halted. If an immediate stop is required, an Error must be raised inside of the execution.
|
|
|
|
///
|
|
|
|
/// This is most often used when serious errors or infinite loops are encountered.
|
|
|
|
pub fn halt(&mut self) {
|
|
|
|
if !self.halted {
|
|
|
|
self.halted = true;
|
|
|
|
log::error!("No more actions will be executed in this movie.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-31 15:41:51 +00:00
|
|
|
/// Resolves a target value to a display object, relative to a starting display object.
|
2020-01-23 02:14:23 +00:00
|
|
|
///
|
|
|
|
/// 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, '_>,
|
2020-01-31 15:41:51 +00:00
|
|
|
start: DisplayObject<'gc>,
|
2020-01-23 02:14:23 +00:00
|
|
|
target: Value<'gc>,
|
2020-06-20 23:02:45 +00:00
|
|
|
) -> Result<Option<DisplayObject<'gc>>, Error<'gc>> {
|
2020-01-23 02:14:23 +00:00
|
|
|
// 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)?;
|
2020-06-18 22:15:26 +00:00
|
|
|
let root = start.root();
|
2020-06-18 22:16:56 +00:00
|
|
|
let start = start.object().coerce_to_object(self, context);
|
2020-01-23 02:14:23 +00:00
|
|
|
Ok(self
|
2020-06-18 22:15:26 +00:00
|
|
|
.resolve_target_path(context, root, start, &path)?
|
2020-01-23 02:14:23 +00:00
|
|
|
.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, '_>,
|
2020-03-10 20:49:07 +00:00
|
|
|
root: DisplayObject<'gc>,
|
|
|
|
start: Object<'gc>,
|
2020-01-23 02:14:23 +00:00
|
|
|
path: &str,
|
2020-06-20 23:02:45 +00:00
|
|
|
) -> Result<Option<Object<'gc>>, Error<'gc>> {
|
2020-01-23 02:14:23 +00:00
|
|
|
// Empty path resolves immediately to start clip.
|
|
|
|
if path.is_empty() {
|
2020-03-10 20:49:07 +00:00
|
|
|
return Ok(Some(start));
|
2020-01-23 02:14:23 +00:00
|
|
|
}
|
|
|
|
|
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();
|
2020-03-10 20:49:07 +00:00
|
|
|
let (mut object, mut is_slash_path) = if path[0] == b'/' {
|
2019-08-26 23:38:37 +00:00
|
|
|
path = &path[1..];
|
2020-06-18 22:16:56 +00:00
|
|
|
(root.object().coerce_to_object(self, context), 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
|
|
|
|
2020-03-27 22:16:01 +00:00
|
|
|
let case_sensitive = self.is_case_sensitive();
|
|
|
|
|
2020-01-23 02:14:23 +00:00
|
|
|
// 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()
|
2020-03-27 22:16:01 +00:00
|
|
|
.and_then(|o| o.get_child_by_name(name, case_sensitive))
|
2020-01-23 02:14:23 +00:00
|
|
|
{
|
|
|
|
child.object()
|
2019-08-26 23:38:37 +00:00
|
|
|
} else {
|
2020-05-31 19:07:13 +00:00
|
|
|
object.get(&name, self, context).unwrap()
|
2020-01-23 02:14:23 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
///
|
2020-03-10 20:49:07 +00:00
|
|
|
/// The string first tries to resolve as target path with a variable name, such as
|
|
|
|
/// "a/b/c:foo". The right-most : or . delimits the variable name, with the left side
|
|
|
|
/// identifying the target object path. Note that the variable name on the right can
|
|
|
|
/// contain a slash in this case. This path is resolved on the scope chain; if
|
|
|
|
/// the path does not resolve to an existing property on a scope, the parent scope is
|
|
|
|
/// searched. Undefined is returned if no path resolves successfully.
|
2020-01-23 02:14:23 +00:00
|
|
|
///
|
2020-03-10 20:49:07 +00:00
|
|
|
/// If there is no variable name, but the path contains slashes, the path will still try
|
|
|
|
/// to resolve on the scope chain as above. If this fails to resolve, we consider
|
|
|
|
/// it a simple variable name and fall through to the variable case
|
|
|
|
/// (i.e. "a/b/c" would be a variable named "a/b/c", not a path).
|
2020-01-23 02:14:23 +00:00
|
|
|
///
|
2020-03-10 20:49:07 +00:00
|
|
|
/// Finally, if none of the above applies, it is a normal variable name resovled via the
|
|
|
|
/// scope chain.
|
2020-01-23 02:14:23 +00:00
|
|
|
pub fn get_variable<'s>(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
path: &'s str,
|
2020-06-20 23:02:45 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error<'gc>> {
|
2020-01-23 02:14:23 +00:00
|
|
|
// Resolve a variable path for a GetVariable action.
|
2020-01-30 04:21:06 +00:00
|
|
|
let start = self.target_clip_or_root();
|
2020-01-23 02:14:23 +00:00
|
|
|
|
|
|
|
// 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,
|
|
|
|
});
|
2020-03-10 20:49:07 +00:00
|
|
|
|
2020-01-23 02:14:23 +00:00
|
|
|
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) };
|
2020-03-10 20:49:07 +00:00
|
|
|
|
|
|
|
let mut current_scope = Some(self.current_stack_frame().unwrap().read().scope_cell());
|
|
|
|
while let Some(scope) = current_scope {
|
|
|
|
if let Some(object) =
|
|
|
|
self.resolve_target_path(context, start.root(), *scope.read().locals(), path)?
|
|
|
|
{
|
2020-03-27 05:24:59 +00:00
|
|
|
if object.has_property(self, context, var_name) {
|
2020-05-31 19:07:13 +00:00
|
|
|
return Ok(object.get(var_name, self, context)?.into());
|
2020-03-10 20:49:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
current_scope = scope.read().parent_cell();
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2020-03-10 20:49:07 +00:00
|
|
|
|
|
|
|
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 {
|
2020-03-10 20:49:07 +00:00
|
|
|
let mut current_scope = Some(self.current_stack_frame().unwrap().read().scope_cell());
|
|
|
|
while let Some(scope) = current_scope {
|
|
|
|
if let Some(object) =
|
|
|
|
self.resolve_target_path(context, start.root(), *scope.read().locals(), path)?
|
|
|
|
{
|
|
|
|
return Ok(object.into());
|
|
|
|
}
|
|
|
|
current_scope = scope.read().parent_cell();
|
2020-01-23 02:14:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
///
|
2020-03-10 20:49:07 +00:00
|
|
|
/// The string first tries to resolve as target path with a variable name, such as
|
|
|
|
/// "a/b/c:foo". The right-most : or . delimits the variable name, with the left side
|
|
|
|
/// identifying the target object path. Note that the variable name on the right can
|
|
|
|
/// contain a slash in this case. This target path (sans variable) is resolved on the
|
|
|
|
/// scope chain; if the path does not resolve to an existing property on a scope, the
|
|
|
|
/// parent scope is searched. If the path does not resolve on any scope, the set fails
|
|
|
|
/// and returns immediately. If the path does resolve, the variable name is created
|
|
|
|
/// or overwritten on the target scope.
|
2020-01-23 02:14:23 +00:00
|
|
|
///
|
|
|
|
/// 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.
|
2020-03-10 20:49:07 +00:00
|
|
|
///
|
|
|
|
/// If the string does not resolve as a path, the path is considered a normal variable
|
|
|
|
/// name and is set on the scope chain as usual.
|
2020-01-23 02:14:23 +00:00
|
|
|
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>,
|
2020-06-20 23:02:45 +00:00
|
|
|
) -> Result<(), Error<'gc>> {
|
2020-01-23 02:14:23 +00:00
|
|
|
// Resolve a variable path for a GetVariable action.
|
2020-01-30 04:21:06 +00:00
|
|
|
let start = self.target_clip_or_root();
|
2020-01-23 02:14:23 +00:00
|
|
|
|
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) };
|
2020-03-10 20:49:07 +00:00
|
|
|
|
|
|
|
let mut current_scope = Some(self.current_stack_frame().unwrap().read().scope_cell());
|
|
|
|
while let Some(scope) = current_scope {
|
|
|
|
if let Some(object) =
|
|
|
|
self.resolve_target_path(context, start.root(), *scope.read().locals(), path)?
|
|
|
|
{
|
|
|
|
object.set(var_name, value, self, context)?;
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
current_scope = scope.read().parent_cell();
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
2020-03-10 20:49:07 +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
|
|
|
}
|
|
|
|
|
2020-06-23 04:07:27 +00:00
|
|
|
/// Resolves a path for text field variable binding.
|
|
|
|
/// Returns the parent object that owns the variable, and the variable name.
|
|
|
|
/// Returns `None` if the path does not yet point to a valid object.
|
|
|
|
/// TODO: This can probably be merged with some of the above `resolve_target_path` methods.
|
2020-06-17 17:37:11 +00:00
|
|
|
pub fn resolve_text_field_variable_path<'s>(
|
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-06-23 04:07:27 +00:00
|
|
|
text_field_parent: DisplayObject<'gc>,
|
2020-06-17 17:37:11 +00:00
|
|
|
path: &'s str,
|
|
|
|
) -> Result<Option<(Object<'gc>, &'s str)>, Error<'gc>> {
|
|
|
|
// Resolve a variable path for a GetVariable action.
|
2020-06-23 04:07:27 +00:00
|
|
|
let start = text_field_parent;
|
2020-06-17 17:37:11 +00:00
|
|
|
|
|
|
|
// 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) };
|
|
|
|
|
|
|
|
let mut current_scope = Some(self.current_stack_frame().unwrap().read().scope_cell());
|
|
|
|
while let Some(scope) = current_scope {
|
|
|
|
if let Some(object) =
|
|
|
|
self.resolve_target_path(context, start.root(), *scope.read().locals(), path)?
|
|
|
|
{
|
|
|
|
return Ok(Some((object, var_name)));
|
|
|
|
}
|
|
|
|
current_scope = scope.read().parent_cell();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally! It's a plain old variable name.
|
|
|
|
// Resolve using scope chain, as normal.
|
|
|
|
if let Value::Object(object) = start.object() {
|
|
|
|
Ok(Some((object, path)))
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-24 07:41:55 +00:00
|
|
|
/// Resolve a level by ID.
|
2020-01-12 17:34:16 +00:00
|
|
|
///
|
2020-02-24 07:41:55 +00:00
|
|
|
/// If the level does not exist, then it will be created and instantiated
|
2020-01-12 17:34:16 +00:00
|
|
|
/// with a script object.
|
2020-02-24 07:41:55 +00:00
|
|
|
pub fn resolve_level(
|
2020-01-20 21:38:23 +00:00
|
|
|
&mut self,
|
2020-01-12 17:34:16 +00:00
|
|
|
level_id: u32,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> DisplayObject<'gc> {
|
2020-02-24 07:41:55 +00:00
|
|
|
if let Some(level) = context.levels.get(&level_id) {
|
|
|
|
*level
|
2020-01-12 17:34:16 +00:00
|
|
|
} else {
|
2020-04-20 09:38:01 +00:00
|
|
|
let mut level: DisplayObject<'_> = MovieClip::new(
|
|
|
|
SwfSlice::empty(self.base_clip().movie().unwrap()),
|
|
|
|
context.gc_context,
|
|
|
|
)
|
|
|
|
.into();
|
2020-01-12 17:34:16 +00:00
|
|
|
|
2020-02-24 07:41:55 +00:00
|
|
|
level.set_depth(context.gc_context, level_id as i32);
|
|
|
|
context.levels.insert(level_id, level);
|
2020-06-12 08:07:12 +00:00
|
|
|
level.post_instantiation(self, context, level, None, false);
|
2020-01-12 17:34:16 +00:00
|
|
|
|
2020-02-24 07:41:55 +00:00
|
|
|
level
|
2020-01-12 17:34:16 +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
|
|
|
}
|
|
|
|
|
2020-06-25 21:51:42 +00:00
|
|
|
/// Obtain the value of `_root`.
|
|
|
|
pub fn root_object(&self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> {
|
|
|
|
self.base_clip().root().object()
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-25 21:51:42 +00:00
|
|
|
/// Obtain the value of `_global`.
|
|
|
|
pub fn global_object(&self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> {
|
|
|
|
Value::Object(self.globals)
|
2019-08-26 23:38:37 +00:00
|
|
|
}
|
|
|
|
|
2020-06-25 21:51:42 +00:00
|
|
|
/// Obtain a reference to `_global`.
|
|
|
|
pub fn global_object_cell(&self) -> Object<'gc> {
|
|
|
|
self.globals
|
2019-10-04 02:42:32 +00:00
|
|
|
}
|
|
|
|
|
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 23:38:37 +00:00
|
|
|
}
|
2019-09-15 21:57:35 +00:00
|
|
|
|
2020-03-27 00:52:50 +00:00
|
|
|
/// Returns whether the given SWF version is case-sensitive.
|
|
|
|
/// SWFv7 and above is case-sensitive.
|
|
|
|
pub fn is_swf_case_sensitive(swf_version: u8) -> bool {
|
|
|
|
swf_version > 6
|
|
|
|
}
|
|
|
|
|
2019-09-15 21:57:35 +00:00
|
|
|
/// Utility function used by `Avm1::action_wait_for_frame` and
|
|
|
|
/// `Avm1::action_wait_for_frame_2`.
|
2020-06-20 12:57:53 +00:00
|
|
|
fn skip_actions(reader: &mut Reader<'_>, num_actions_to_skip: u8) {
|
2019-09-15 21:57:35 +00:00
|
|
|
for _ in 0..num_actions_to_skip {
|
2020-06-20 12:57:53 +00:00
|
|
|
if let Err(e) = reader.read_action() {
|
|
|
|
log::warn!("Couldn't skip action: {}", e);
|
|
|
|
}
|
2019-09-15 21:57:35 +00:00
|
|
|
}
|
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)
|
2019-11-12 20:05:18 +00:00
|
|
|
.map(|o| o.as_bool(context.swf.version()))
|
2019-12-21 23:37:27 +00:00
|
|
|
.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)
|
2020-06-19 18:03:04 +00:00
|
|
|
.coerce_to_f64(avm, context)
|
2019-12-21 23:37:27 +00:00
|
|
|
.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)
|
2020-06-19 18:03:04 +00:00
|
|
|
.coerce_to_f64(avm, context)
|
2019-12-21 23:37:27 +00:00
|
|
|
.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)
|
2020-06-19 18:03:04 +00:00
|
|
|
.coerce_to_f64(avm, context)
|
2019-12-21 23:37:27 +00:00
|
|
|
.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)
|
2020-06-19 18:03:04 +00:00
|
|
|
.coerce_to_f64(avm, context)
|
2019-12-21 23:37:27 +00:00
|
|
|
.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);
|
|
|
|
}
|