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;
|
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-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-06-28 10:07:27 +00:00
|
|
|
use crate::display_object::DisplayObject;
|
2020-01-10 23:28:49 +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;
|
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-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-28 10:07:27 +00:00
|
|
|
pub 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-28 16:32:38 +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
|
|
|
/// 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-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-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-09-19 00:47:21 +00:00
|
|
|
/// Add a stack frame that executes code in timeline scope
|
2020-06-28 10:07:27 +00:00
|
|
|
pub fn run_stack_frame_for_action(
|
2019-10-06 21:45:14 +00:00
|
|
|
&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;
|
|
|
|
}
|
|
|
|
|
2020-06-28 17:15:01 +00:00
|
|
|
self.run_in_avm(
|
|
|
|
action_context,
|
|
|
|
swf_version,
|
|
|
|
active_clip,
|
|
|
|
|activation, context| {
|
|
|
|
let clip_obj = active_clip.object().coerce_to_object(activation, context);
|
|
|
|
let child_scope = GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
Scope::new(
|
|
|
|
activation.activation().read().scope_cell(),
|
|
|
|
scope::ScopeClass::Target,
|
|
|
|
clip_obj,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
let child_activation = GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
Activation::from_action(
|
|
|
|
swf_version,
|
|
|
|
code,
|
|
|
|
child_scope,
|
|
|
|
activation.avm().constant_pool,
|
|
|
|
active_clip,
|
|
|
|
clip_obj,
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
if let Err(e) = activation.run_child_activation(child_activation, context) {
|
|
|
|
root_error_handler(activation, context, e);
|
|
|
|
}
|
|
|
|
},
|
2020-06-28 10:07:27 +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
|
2020-06-28 10:07:27 +00:00
|
|
|
pub fn run_with_stack_frame_for_display_object<'a, F, R>(
|
2020-06-23 04:07:27 +00:00
|
|
|
&mut self,
|
|
|
|
active_clip: DisplayObject<'gc>,
|
|
|
|
swf_version: u8,
|
2020-06-28 10:07:27 +00:00
|
|
|
action_context: &mut UpdateContext<'a, 'gc, '_>,
|
|
|
|
function: F,
|
|
|
|
) -> R
|
|
|
|
where
|
|
|
|
for<'b> F: FnOnce(&mut StackFrame<'b, 'gc>, &mut UpdateContext<'a, 'gc, '_>) -> R,
|
|
|
|
{
|
2020-06-23 04:07:27 +00:00
|
|
|
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),
|
|
|
|
);
|
2020-06-28 10:07:27 +00:00
|
|
|
let activation = GcCell::allocate(
|
2020-06-23 04:07:27 +00:00
|
|
|
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,
|
|
|
|
),
|
2020-06-28 10:07:27 +00:00
|
|
|
);
|
2020-06-28 17:15:01 +00:00
|
|
|
let mut stack_frame = StackFrame::new(self, None, activation);
|
|
|
|
function(&mut stack_frame, action_context)
|
2020-06-23 04:07:27 +00:00
|
|
|
}
|
|
|
|
|
2019-10-29 04:07:47 +00:00
|
|
|
/// Add a stack frame that executes code in initializer scope
|
2020-06-28 10:07:27 +00:00
|
|
|
pub fn run_stack_frame_for_init_action(
|
2019-10-29 04:07:47 +00:00
|
|
|
&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;
|
|
|
|
}
|
|
|
|
|
2020-06-28 17:15:01 +00:00
|
|
|
self.run_in_avm(
|
|
|
|
action_context,
|
|
|
|
swf_version,
|
|
|
|
active_clip,
|
|
|
|
|activation, context| {
|
|
|
|
let clip_obj = active_clip.object().coerce_to_object(activation, context);
|
|
|
|
let child_scope = GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
Scope::new(
|
|
|
|
activation.activation().read().scope_cell(),
|
|
|
|
scope::ScopeClass::Target,
|
|
|
|
clip_obj,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
activation.avm().push(Value::Undefined);
|
|
|
|
let child_activation = GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
Activation::from_action(
|
|
|
|
swf_version,
|
|
|
|
code,
|
|
|
|
child_scope,
|
|
|
|
activation.avm().constant_pool,
|
|
|
|
active_clip,
|
|
|
|
clip_obj,
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
if let Err(e) = activation.run_child_activation(child_activation, context) {
|
|
|
|
root_error_handler(activation, context, e);
|
|
|
|
}
|
|
|
|
},
|
2020-06-28 10:07:27 +00:00
|
|
|
);
|
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.
|
2020-06-28 10:07:27 +00:00
|
|
|
pub fn run_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;
|
|
|
|
}
|
|
|
|
|
2020-06-28 10:07:27 +00:00
|
|
|
fn caller<'gc>(
|
|
|
|
activation: &mut StackFrame<'_, 'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
obj: Object<'gc>,
|
|
|
|
name: &str,
|
|
|
|
args: &[Value<'gc>],
|
|
|
|
) {
|
2020-06-28 16:19:58 +00:00
|
|
|
let search_result =
|
|
|
|
search_prototype(Some(obj), name, activation, context, obj).map(|r| (r.0, r.1));
|
2020-06-28 10:07:27 +00:00
|
|
|
|
|
|
|
if let Ok((callback, base_proto)) = search_result {
|
|
|
|
let _ = callback.call(activation, context, obj, base_proto, args);
|
|
|
|
}
|
2019-12-18 19:30:21 +00:00
|
|
|
}
|
2020-06-28 17:15:01 +00:00
|
|
|
self.run_in_avm(context, swf_version, active_clip, |activation, context| {
|
2020-06-28 10:07:27 +00:00
|
|
|
caller(activation, context, obj, name, args)
|
|
|
|
});
|
2019-12-18 19:30:21 +00:00
|
|
|
}
|
|
|
|
|
2020-06-28 10:07:27 +00:00
|
|
|
/// Run a function within the scope of an activation.
|
2020-06-28 17:15:01 +00:00
|
|
|
///
|
|
|
|
/// This is intended to be used to create a new frame stack from nothing.
|
|
|
|
pub fn run_in_avm<'a, F, R>(
|
2020-06-28 10:07:27 +00:00
|
|
|
&mut self,
|
|
|
|
context: &mut UpdateContext<'a, 'gc, '_>,
|
2020-06-28 17:15:01 +00:00
|
|
|
swf_version: u8,
|
|
|
|
base_clip: DisplayObject<'gc>,
|
2020-06-28 10:07:27 +00:00
|
|
|
function: F,
|
|
|
|
) -> R
|
|
|
|
where
|
|
|
|
for<'b> F: FnOnce(&mut StackFrame<'b, 'gc>, &mut UpdateContext<'a, 'gc, '_>) -> R,
|
|
|
|
{
|
2020-06-28 17:15:01 +00:00
|
|
|
let activation = GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
Activation::from_nothing(
|
|
|
|
swf_version,
|
|
|
|
self.global_object_cell(),
|
|
|
|
context.gc_context,
|
|
|
|
base_clip,
|
|
|
|
),
|
|
|
|
);
|
2020-06-28 16:32:38 +00:00
|
|
|
let mut stack_frame = StackFrame::new(self, None, activation);
|
2020-06-28 13:45:44 +00:00
|
|
|
function(&mut stack_frame, context)
|
2020-04-12 18:50:34 +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>],
|
|
|
|
) {
|
2020-06-28 17:15:01 +00:00
|
|
|
self.run_in_avm(context, swf_version, active_clip, |activation, context| {
|
2020-06-28 10:07:27 +00:00
|
|
|
let listeners = activation.avm().system_listeners.get(listener);
|
|
|
|
let mut handlers = listeners.prepare_handlers(activation, context, method);
|
2019-12-18 16:45:20 +00:00
|
|
|
|
2020-06-28 10:07:27 +00:00
|
|
|
for (listener, handler) in handlers.drain(..) {
|
|
|
|
let _ = handler.call(activation, context, listener, None, &args);
|
|
|
|
}
|
|
|
|
});
|
2019-12-18 16:45:20 +00:00
|
|
|
}
|
|
|
|
|
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.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 `_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-06-28 10:07:27 +00:00
|
|
|
pub fn root_error_handler<'gc>(
|
|
|
|
activation: &mut StackFrame<'_, 'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
error: Error<'gc>,
|
|
|
|
) {
|
|
|
|
if let Error::ThrownValue(error) = error {
|
|
|
|
let string = error
|
|
|
|
.coerce_to_string(activation, context)
|
|
|
|
.unwrap_or_else(|_| Cow::Borrowed("undefined"));
|
|
|
|
log::info!(target: "avm_trace", "{}", string);
|
|
|
|
} else {
|
|
|
|
log::error!("Uncaught error: {:?}", error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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>,
|
2020-06-28 10:07:27 +00:00
|
|
|
activation: &mut StackFrame<'_, 'gc>,
|
2019-12-21 23:37:27 +00:00
|
|
|
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-28 10:07:27 +00:00
|
|
|
.coerce_to_f64(activation, 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-28 10:07:27 +00:00
|
|
|
.coerce_to_f64(activation, 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-28 10:07:27 +00:00
|
|
|
.coerce_to_f64(activation, 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-28 10:07:27 +00:00
|
|
|
.coerce_to_f64(activation, 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);
|
|
|
|
}
|