2019-07-19 08:32:41 +00:00
|
|
|
use crate::prelude::*;
|
|
|
|
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::io::Cursor;
|
|
|
|
use swf::avm1::read::Reader;
|
|
|
|
|
2019-08-14 19:39:26 +00:00
|
|
|
pub struct ActionContext<'a, 'gc, 'gc_context> {
|
|
|
|
pub gc_context: gc_arena::MutationContext<'gc, 'gc_context>,
|
2019-07-19 08:32:41 +00:00
|
|
|
pub global_time: u64,
|
|
|
|
pub root: DisplayNode<'gc>,
|
|
|
|
pub start_clip: DisplayNode<'gc>,
|
|
|
|
pub active_clip: DisplayNode<'gc>,
|
2019-08-15 20:48:51 +00:00
|
|
|
pub audio: &'a mut dyn crate::backend::audio::AudioBackend,
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Avm1 {
|
|
|
|
swf_version: u8,
|
|
|
|
stack: Vec<Value>,
|
|
|
|
rng: SmallRng,
|
|
|
|
constant_pool: Vec<String>,
|
|
|
|
locals: HashMap<String, Value>,
|
|
|
|
}
|
|
|
|
|
2019-08-15 20:48:51 +00:00
|
|
|
type Error = Box<dyn std::error::Error>;
|
2019-07-19 08:32:41 +00:00
|
|
|
|
|
|
|
impl Avm1 {
|
|
|
|
pub fn new(swf_version: u8) -> Self {
|
|
|
|
Self {
|
|
|
|
swf_version,
|
|
|
|
stack: vec![],
|
|
|
|
rng: SmallRng::from_seed([0u8; 16]), // TODO(Herschel): Get a proper seed on all platforms.
|
|
|
|
constant_pool: vec![],
|
|
|
|
locals: HashMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn do_action(&mut self, context: &mut ActionContext, code: &[u8]) -> Result<(), Error> {
|
|
|
|
let mut reader = Reader::new(Cursor::new(code), self.swf_version);
|
|
|
|
|
|
|
|
while let Some(action) = reader.read_action()? {
|
|
|
|
use swf::avm1::types::Action;
|
2019-08-17 01:30:28 +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),
|
|
|
|
Action::CharToAscii => self.action_char_to_ascii(context),
|
2019-07-19 08:32:41 +00:00
|
|
|
Action::ConstantPool(constant_pool) => {
|
2019-08-17 01:30:28 +00:00
|
|
|
self.action_constant_pool(context, &constant_pool[..])
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
2019-08-17 01:30:28 +00:00
|
|
|
Action::Decrement => self.action_decrement(context),
|
2019-07-19 08:32:41 +00:00
|
|
|
Action::DefineFunction {
|
|
|
|
name,
|
|
|
|
params,
|
|
|
|
actions,
|
2019-08-17 01:30:28 +00:00
|
|
|
} => self.action_define_function(context, &name, ¶ms[..], &actions[..]),
|
|
|
|
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),
|
|
|
|
Action::Equals => self.action_equals(context),
|
|
|
|
Action::Equals2 => self.action_equals_2(context),
|
|
|
|
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),
|
2019-07-19 08:32:41 +00:00
|
|
|
Action::GetUrl2 {
|
|
|
|
send_vars_method,
|
|
|
|
is_target_sprite,
|
|
|
|
is_load_vars,
|
2019-08-17 01:30:28 +00:00
|
|
|
} => {
|
|
|
|
self.action_get_url_2(context, send_vars_method, is_target_sprite, is_load_vars)
|
|
|
|
}
|
|
|
|
Action::GotoFrame(frame) => self.action_goto_frame(context, frame),
|
2019-07-19 08:32:41 +00:00
|
|
|
Action::GotoFrame2 {
|
|
|
|
set_playing,
|
|
|
|
scene_offset,
|
2019-08-17 01:30:28 +00:00
|
|
|
} => self.action_goto_frame_2(context, set_playing, scene_offset),
|
|
|
|
Action::GotoLabel(label) => self.action_goto_label(context, &label),
|
|
|
|
Action::If { offset } => self.action_if(context, offset, &mut reader),
|
|
|
|
Action::Increment => self.action_increment(context),
|
|
|
|
Action::InitArray => self.action_init_array(context),
|
|
|
|
Action::InitObject => self.action_init_object(context),
|
|
|
|
Action::Jump { offset } => self.action_jump(context, offset, &mut reader),
|
|
|
|
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.play(context),
|
|
|
|
Action::Pop => self.action_pop(context),
|
|
|
|
Action::PreviousFrame => self.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::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),
|
|
|
|
Action::StringAdd => self.action_string_add(context),
|
|
|
|
Action::StringEquals => self.action_string_equals(context),
|
|
|
|
Action::StringExtract => self.action_string_extract(context),
|
|
|
|
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),
|
2019-07-19 08:32:41 +00:00
|
|
|
Action::WaitForFrame {
|
|
|
|
frame,
|
|
|
|
num_actions_to_skip,
|
2019-08-17 01:30:28 +00:00
|
|
|
} => self.action_wait_for_frame(context, frame, num_actions_to_skip, &mut reader),
|
2019-07-19 08:32:41 +00:00
|
|
|
Action::WaitForFrame2 {
|
|
|
|
num_actions_to_skip,
|
2019-08-17 01:30:28 +00:00
|
|
|
} => self.action_wait_for_frame_2(context, num_actions_to_skip, &mut reader),
|
|
|
|
Action::With { .. } => self.action_with(context),
|
|
|
|
_ => self.unknown_op(context, action),
|
|
|
|
};
|
|
|
|
if let Err(ref e) = result {
|
|
|
|
log::error!("AVM1 error: {}", e);
|
|
|
|
return result;
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn resolve_slash_path<'gc>(
|
2019-08-14 19:39:26 +00:00
|
|
|
start: DisplayNode<'gc>,
|
|
|
|
root: DisplayNode<'gc>,
|
|
|
|
mut path: &str,
|
2019-07-19 08:32:41 +00:00
|
|
|
) -> Option<DisplayNode<'gc>> {
|
2019-08-14 19:39:26 +00:00
|
|
|
let mut cur_clip = if path.bytes().nth(0).unwrap_or(0) == b'/' {
|
|
|
|
path = &path[1..];
|
|
|
|
root
|
|
|
|
} else {
|
|
|
|
start
|
|
|
|
};
|
|
|
|
if !path.is_empty() {
|
|
|
|
for name in path.split('/') {
|
|
|
|
let next_clip = if let Some(clip) = cur_clip.read().as_movie_clip() {
|
|
|
|
if let Some(child) = clip.get_child_by_name(name) {
|
|
|
|
*child
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
};
|
|
|
|
cur_clip = next_clip;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(cur_clip)
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 18:29:00 +00:00
|
|
|
pub fn resolve_slash_path_variable<'gc, 's>(
|
|
|
|
start: DisplayNode<'gc>,
|
|
|
|
root: DisplayNode<'gc>,
|
|
|
|
path: &'s str,
|
|
|
|
) -> Option<(DisplayNode<'gc>, &'s str)> {
|
|
|
|
if !path.is_empty() {
|
|
|
|
let mut var_iter = path.splitn(2, ':');
|
|
|
|
match (var_iter.next(), var_iter.next()) {
|
|
|
|
(Some(var_name), None) => return Some((start, var_name)),
|
2019-08-17 01:30:28 +00:00
|
|
|
(Some(path), Some(var_name)) => {
|
|
|
|
if let Some(node) = Self::resolve_slash_path(start, root, path) {
|
|
|
|
return Some((node, var_name));
|
|
|
|
}
|
2019-08-15 18:29:00 +00:00
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
fn push(&mut self, value: impl Into<Value>) {
|
|
|
|
self.stack.push(value.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pop(&mut self) -> Result<Value, Error> {
|
|
|
|
self.stack.pop().ok_or_else(|| "Stack underflow".into())
|
|
|
|
}
|
|
|
|
|
2019-08-14 19:39:26 +00:00
|
|
|
fn unknown_op(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
action: swf::avm1::types::Action,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
log::error!("Unknown AVM1 opcode: {:?}", action);
|
2019-07-19 08:32:41 +00:00
|
|
|
Err("Unknown op".into())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_add(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
self.push(Value::Number(b.into_number_v1() + a.into_number_v1()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_add_2(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// ECMA-262 s. 11.6.1
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
// TODO(Herschel):
|
|
|
|
if let Value::String(a) = a {
|
|
|
|
let mut s = b.into_string();
|
|
|
|
s.push_str(&a);
|
|
|
|
self.push(Value::String(s));
|
|
|
|
} else if let Value::String(mut b) = b {
|
|
|
|
b.push_str(&a.into_string());
|
|
|
|
self.push(Value::String(b));
|
|
|
|
} else {
|
|
|
|
self.push(Value::Number(b.into_number() + a.into_number()));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_and(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// AS1 logical and
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
let result = b.into_number_v1() != 0.0 && a.into_number_v1() != 0.0;
|
|
|
|
self.push(Value::from_bool_v1(result, self.swf_version));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_ascii_to_char(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Results on incorrect operands?
|
|
|
|
let val = (self.pop()?.as_f64()? as u8) as char;
|
|
|
|
self.push(Value::String(val.to_string()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_char_to_ascii(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Results on incorrect operands?
|
|
|
|
let s = self.pop()?.into_string();
|
|
|
|
let result = s.bytes().nth(0).unwrap_or(0);
|
|
|
|
self.push(Value::Number(result.into()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_bit_and(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?.as_u32()?;
|
|
|
|
let b = self.pop()?.as_u32()?;
|
|
|
|
let result = a & b;
|
|
|
|
self.push(Value::Number(result.into()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_bit_lshift(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?.as_i32()? & 0b11111; // Only 5 bits used for shift count
|
|
|
|
let b = self.pop()?.as_i32()?;
|
|
|
|
let result = b << a;
|
|
|
|
self.push(Value::Number(result.into()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_bit_or(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?.as_u32()?;
|
|
|
|
let b = self.pop()?.as_u32()?;
|
|
|
|
let result = a | b;
|
|
|
|
self.push(Value::Number(result.into()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_bit_rshift(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?.as_i32()? & 0b11111; // Only 5 bits used for shift count
|
|
|
|
let b = self.pop()?.as_i32()?;
|
|
|
|
let result = b >> a;
|
|
|
|
self.push(Value::Number(result.into()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_bit_urshift(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?.as_u32()? & 0b11111; // Only 5 bits used for shift count
|
|
|
|
let b = self.pop()?.as_u32()?;
|
|
|
|
let result = b >> a;
|
|
|
|
self.push(Value::Number(result.into()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_bit_xor(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?.as_u32()?;
|
|
|
|
let b = self.pop()?.as_u32()?;
|
|
|
|
let result = b ^ a;
|
|
|
|
self.push(Value::Number(result.into()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_call(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _val = self.pop()?;
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: Call".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_call_function(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _fn_name = self.pop()?.as_string()?;
|
|
|
|
let num_args = self.pop()?.as_i64()?; // TODO(Herschel): max arg count?
|
|
|
|
for _ in 0..num_args {
|
|
|
|
self.pop()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.stack.push(Value::Undefined);
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: CallFunction".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_call_method(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _method_name = self.pop()?.as_string()?;
|
|
|
|
let _object = self.pop()?.as_object()?;
|
|
|
|
let num_args = self.pop()?.as_i64()?; // TODO(Herschel): max arg count?
|
|
|
|
for _ in 0..num_args {
|
|
|
|
self.pop()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.stack.push(Value::Undefined);
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: CallMethod".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_constant_pool(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
constant_pool: &[String],
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
self.constant_pool = constant_pool.to_vec();
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_decrement(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?.into_number();
|
|
|
|
self.push(Value::Number(a - 1.0));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_define_function(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
_name: &str,
|
|
|
|
_params: &[String],
|
|
|
|
_actions: &[swf::avm1::types::Action],
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: DefineFunction".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_define_local(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let value = self.pop()?;
|
|
|
|
let name = self.pop()?;
|
|
|
|
self.locals.insert(name.as_string()?.clone(), value);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_define_local_2(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let name = self.pop()?;
|
|
|
|
self.locals
|
|
|
|
.insert(name.as_string()?.clone(), Value::Undefined);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_delete(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _name = self.pop()?.as_string()?;
|
|
|
|
let _object = self.pop()?.as_object()?;
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: Delete".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
// TODO(Herschel)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_delete_2(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _name = self.pop()?.as_string()?;
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: Delete2".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
// TODO(Herschel)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_divide(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// AS1 divide
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
|
|
|
|
// 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#.""
|
|
|
|
// Seems to be unture for SWF v4, I get 1.#INF.
|
|
|
|
|
|
|
|
self.push(Value::Number(b.into_number_v1() / a.into_number_v1()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_end_drag(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel)
|
2019-08-19 23:37:10 +00:00
|
|
|
log::error!("Unimplemented action: EndDrag");
|
|
|
|
Ok(())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_enumerate(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _name = self.pop()?.as_string()?;
|
|
|
|
self.push(Value::Null); // Sentinel that indicates end of enumeration
|
|
|
|
// TODO(Herschel): Push each property name onto the stack
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: Enumerate".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::float_cmp)]
|
|
|
|
fn action_equals(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// AS1 equality
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
let result = b.into_number_v1() == a.into_number_v1();
|
|
|
|
self.push(Value::from_bool_v1(result, self.swf_version));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::float_cmp)]
|
|
|
|
fn action_equals_2(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// Version >=5 equality
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
let result = match (b, a) {
|
|
|
|
(Value::Undefined, Value::Undefined) => true,
|
|
|
|
(Value::Null, Value::Null) => true,
|
|
|
|
(Value::Null, Value::Undefined) => true,
|
|
|
|
(Value::Undefined, Value::Null) => true,
|
|
|
|
(Value::Number(a), Value::Number(b)) => a == b,
|
|
|
|
(Value::String(a), Value::String(b)) => a == b,
|
|
|
|
(Value::Object(_a), Value::Object(_b)) => false, // TODO(Herschel)
|
|
|
|
(Value::String(a), Value::Number(b)) => a.parse().unwrap_or(std::f64::NAN) == b,
|
|
|
|
(Value::Number(a), Value::String(b)) => a == b.parse().unwrap_or(std::f64::NAN),
|
|
|
|
_ => false,
|
|
|
|
};
|
|
|
|
self.push(Value::Bool(result));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_get_member(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _name = self.pop()?.as_string()?;
|
|
|
|
let _object = self.pop()?.as_object()?;
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: GetMember".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
2019-08-14 19:39:26 +00:00
|
|
|
fn action_get_property(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let prop_index = self.pop()?.as_u32()? as usize;
|
|
|
|
let clip_path = self.pop()?;
|
|
|
|
let ret = if let Some(clip) =
|
|
|
|
Avm1::resolve_slash_path(context.active_clip, context.root, clip_path.as_string()?)
|
|
|
|
{
|
|
|
|
if let Some(clip) = clip.read().as_movie_clip() {
|
|
|
|
match prop_index {
|
|
|
|
0 => Value::Number(f64::from(clip.x())),
|
|
|
|
1 => Value::Number(f64::from(clip.y())),
|
|
|
|
2 => Value::Number(f64::from(clip.x_scale())),
|
|
|
|
3 => Value::Number(f64::from(clip.y_scale())),
|
|
|
|
4 => Value::Number(f64::from(clip.current_frame())),
|
|
|
|
5 => Value::Number(f64::from(clip.total_frames())),
|
2019-08-17 01:03:02 +00:00
|
|
|
10 => Value::Number(f64::from(clip.rotation())),
|
2019-08-14 19:39:26 +00:00
|
|
|
12 => Value::Number(f64::from(clip.frames_loaded())),
|
2019-08-17 01:30:28 +00:00
|
|
|
_ => {
|
|
|
|
log::error!("GetProperty: Unimplemented property index {}", prop_index);
|
|
|
|
Value::Undefined
|
|
|
|
}
|
2019-08-14 19:39:26 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Value::Undefined
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Value::Undefined
|
|
|
|
};
|
|
|
|
self.push(ret);
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_get_time(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
self.stack.push(Value::Number(context.global_time as f64));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-15 18:29:00 +00:00
|
|
|
fn action_get_variable(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// Flash 4-style variable
|
|
|
|
let var_path = self.pop()?;
|
2019-08-17 01:30:28 +00:00
|
|
|
if let Some((node, var_name)) = Self::resolve_slash_path_variable(
|
|
|
|
context.active_clip,
|
|
|
|
context.root,
|
|
|
|
var_path.as_string()?,
|
|
|
|
) {
|
2019-08-15 18:29:00 +00:00
|
|
|
if let Some(clip) = node.read().as_movie_clip() {
|
|
|
|
self.push(clip.get_variable(var_name));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.push(Value::Undefined);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
fn action_get_url(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
_url: &str,
|
|
|
|
_target: &str,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Noop for now. Need a UI/ActionScript/network backend
|
|
|
|
// to handle network requests appropriately for the platform.
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_get_url_2(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
_method: swf::avm1::types::SendVarsMethod,
|
|
|
|
_is_target_sprite: bool,
|
|
|
|
_is_load_vars: bool,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Noop for now. Need a UI/ActionScript/network backend
|
|
|
|
// to handle network requests appropriately for the platform.
|
|
|
|
let _url = self.pop()?.into_string();
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-14 19:39:26 +00:00
|
|
|
fn action_goto_frame(&mut self, context: &mut ActionContext, frame: u16) -> Result<(), Error> {
|
|
|
|
let mut display_object = context.active_clip.write(context.gc_context);
|
|
|
|
let clip = display_object.as_movie_clip_mut().unwrap();
|
2019-08-15 18:29:00 +00:00
|
|
|
clip.goto_frame(frame + 1, true);
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_goto_frame_2(
|
|
|
|
&mut self,
|
2019-08-14 19:39:26 +00:00
|
|
|
context: &mut ActionContext,
|
|
|
|
set_playing: bool,
|
|
|
|
scene_offset: u16,
|
2019-07-19 08:32:41 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Version 4+ gotoAndPlay/gotoAndStop
|
|
|
|
// Param can either be a frame number or a frame label.
|
2019-08-14 19:39:26 +00:00
|
|
|
let mut display_object = context.active_clip.write(context.gc_context);
|
|
|
|
let clip = display_object.as_movie_clip_mut().unwrap();
|
|
|
|
match self.pop()? {
|
|
|
|
Value::Number(frame) => {
|
|
|
|
clip.goto_frame(scene_offset + (frame as u16) + 1, !set_playing)
|
|
|
|
}
|
2019-08-16 01:00:53 +00:00
|
|
|
Value::String(frame_label) => {
|
|
|
|
if let Some(frame) = clip.frame_label_to_number(&frame_label) {
|
|
|
|
clip.goto_frame(scene_offset + frame, !set_playing)
|
|
|
|
} else {
|
2019-08-17 01:30:28 +00:00
|
|
|
log::warn!(
|
|
|
|
"ActionGotoFrame2 failed: Movie clip {} does not contain frame label '{}'",
|
|
|
|
clip.id(),
|
|
|
|
frame_label
|
|
|
|
);
|
2019-08-16 01:00:53 +00:00
|
|
|
}
|
2019-08-14 19:39:26 +00:00
|
|
|
}
|
|
|
|
_ => return Err("Expected frame number or label".into()),
|
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-17 01:30:28 +00:00
|
|
|
fn action_goto_label(&mut self, context: &mut ActionContext, label: &str) -> Result<(), Error> {
|
2019-08-16 00:00:48 +00:00
|
|
|
let mut display_object = context.active_clip.write(context.gc_context);
|
|
|
|
if let Some(clip) = display_object.as_movie_clip_mut() {
|
|
|
|
if let Some(frame) = clip.frame_label_to_number(label) {
|
|
|
|
clip.goto_frame(frame, true);
|
|
|
|
} else {
|
|
|
|
log::warn!("ActionGoToLabel: Frame label '{}' not found", label);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::warn!("ActionGoToLabel: Expected movie clip");
|
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_if(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
jump_offset: i16,
|
|
|
|
reader: &mut Reader<Cursor<&[u8]>>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let val = self.pop()?;
|
|
|
|
if val.as_bool() {
|
|
|
|
use swf::read::SwfRead;
|
|
|
|
let pos = reader.get_inner().position();
|
|
|
|
let new_pos = ((pos as i64) + i64::from(jump_offset)) as u64;
|
|
|
|
reader.get_inner().set_position(new_pos);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_increment(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?.into_number();
|
|
|
|
self.push(Value::Number(a + 1.0));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_init_array(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let num_elements = self.pop()?.as_i64()?;
|
|
|
|
for _ in 0..num_elements {
|
|
|
|
let _value = self.pop()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: InitArray".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_init_object(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let num_props = self.pop()?.as_i64()?;
|
|
|
|
for _ in 0..num_props {
|
|
|
|
let _value = self.pop()?;
|
|
|
|
let _name = self.pop()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: InitObject".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_jump(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
jump_offset: i16,
|
|
|
|
reader: &mut Reader<Cursor<&[u8]>>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Handle out-of-bounds.
|
|
|
|
use swf::read::SwfRead;
|
|
|
|
let pos = reader.get_inner().position();
|
|
|
|
let new_pos = ((pos as i64) + i64::from(jump_offset)) as u64;
|
|
|
|
reader.get_inner().set_position(new_pos);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_less(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// AS1 less than
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
let result = b.into_number_v1() < a.into_number_v1();
|
|
|
|
self.push(Value::from_bool_v1(result, self.swf_version));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_less_2(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// ECMA-262 s. 11.8.5
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
|
|
|
|
let result = match (a, b) {
|
|
|
|
(Value::String(a), Value::String(b)) => b.to_string().bytes().lt(a.to_string().bytes()),
|
|
|
|
(a, b) => b.into_number() < a.into_number(),
|
|
|
|
};
|
|
|
|
|
|
|
|
self.push(Value::Bool(result));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_mb_ascii_to_char(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Results on incorrect operands?
|
|
|
|
use std::convert::TryFrom;
|
|
|
|
let val = char::try_from(self.pop()?.as_f64()? as u32)?;
|
|
|
|
self.push(Value::String(val.to_string()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_mb_char_to_ascii(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Results on incorrect operands?
|
|
|
|
let s = self.pop()?.into_string();
|
|
|
|
let result = s.chars().nth(0).unwrap_or('\0') as u32;
|
|
|
|
self.push(Value::Number(result.into()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_mb_string_extract(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Result with incorrect operands?
|
|
|
|
let len = self.pop()?.as_f64()? as usize;
|
|
|
|
let start = self.pop()?.as_f64()? as usize;
|
|
|
|
let s = self.pop()?.into_string();
|
|
|
|
let result = s[len..len + start].to_string(); // TODO(Herschel): Flash uses UTF-16 internally.
|
|
|
|
self.push(Value::String(result));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_mb_string_length(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Result with non-string operands?
|
|
|
|
let val = self.pop()?.into_string().len();
|
|
|
|
self.push(Value::Number(val as f64));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_multiply(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// AS1 multiply
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
self.push(Value::Number(a.into_number_v1() * b.into_number_v1()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_modulo(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// TODO: Wrong operands?
|
|
|
|
let a = self.pop()?.as_f64()?;
|
|
|
|
let b = self.pop()?.as_f64()?;
|
|
|
|
self.push(Value::Number(a % b));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_not(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// AS1 logical not
|
|
|
|
let val = self.pop()?;
|
|
|
|
let result = val.into_number_v1() == 0.0;
|
|
|
|
self.push(Value::from_bool_v1(result, self.swf_version));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-14 19:39:26 +00:00
|
|
|
fn action_next_frame(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let mut display_object = context.active_clip.write(context.gc_context);
|
|
|
|
let clip = display_object.as_movie_clip_mut().unwrap();
|
|
|
|
clip.next_frame();
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_new_method(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _name = self.pop()?.as_string()?;
|
|
|
|
let _object = self.pop()?.as_object()?;
|
|
|
|
let _num_args = self.pop()?.as_i64()?;
|
|
|
|
self.push(Value::Undefined);
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: NewMethod".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_new_object(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _object = self.pop()?.as_string()?;
|
|
|
|
let num_args = self.pop()?.as_i64()?;
|
|
|
|
for _ in 0..num_args {
|
|
|
|
let _arg = self.pop()?;
|
|
|
|
}
|
|
|
|
self.push(Value::Undefined);
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: NewObject".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_or(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// AS1 logical or
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
let result = b.into_number_v1() != 0.0 || a.into_number_v1() != 0.0;
|
|
|
|
self.push(Value::from_bool_v1(result, self.swf_version));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-14 19:39:26 +00:00
|
|
|
fn play(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let mut display_object = context.active_clip.write(context.gc_context);
|
2019-08-19 14:53:12 +00:00
|
|
|
if let Some(clip) = display_object.as_movie_clip_mut() {
|
|
|
|
clip.play()
|
|
|
|
} else {
|
|
|
|
log::warn!("Play failed: Not a MovieClip");
|
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-14 19:39:26 +00:00
|
|
|
fn prev_frame(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let mut display_object = context.active_clip.write(context.gc_context);
|
|
|
|
let clip = display_object.as_movie_clip_mut().unwrap();
|
|
|
|
clip.prev_frame();
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_pop(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
self.pop()?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_push(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
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),
|
|
|
|
SwfValue::Int(v) => Value::Number(f64::from(*v)),
|
|
|
|
SwfValue::Float(v) => Value::Number(f64::from(*v)),
|
|
|
|
SwfValue::Double(v) => Value::Number(*v),
|
|
|
|
SwfValue::Str(v) => Value::String(v.clone()),
|
2019-08-17 01:30:28 +00:00
|
|
|
SwfValue::Register(_v) => {
|
|
|
|
log::error!("Register push unimplemented");
|
|
|
|
Value::Undefined
|
|
|
|
}
|
2019-08-16 17:41:02 +00:00
|
|
|
SwfValue::ConstantPool(i) => {
|
|
|
|
if let Some(value) = self.constant_pool.get(*i as usize) {
|
|
|
|
Value::String(value.clone())
|
|
|
|
} else {
|
2019-08-17 01:30:28 +00:00
|
|
|
log::warn!(
|
|
|
|
"ActionPush: Constant pool index {} out of range (len = {})",
|
|
|
|
i,
|
|
|
|
self.constant_pool.len()
|
|
|
|
);
|
2019-08-16 17:41:02 +00:00
|
|
|
Value::Undefined
|
|
|
|
}
|
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
};
|
|
|
|
self.push(value);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_push_duplicate(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let val = self.stack.last().ok_or("Stack underflow")?.clone();
|
|
|
|
self.push(val);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_random_number(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let max = self.pop()?.as_f64()? as u32;
|
|
|
|
let val = self.rng.gen_range(0, max);
|
|
|
|
self.push(Value::Number(val.into()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_remove_sprite(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _target = self.pop()?.into_string();
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: RemoveSprite".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_return(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _result = self.pop()?;
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: Return".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_set_member(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _value = self.pop()?;
|
|
|
|
let _name = self.pop()?;
|
|
|
|
let _object = self.pop()?;
|
|
|
|
// TODO(Herschel)
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: SetMember".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 21:42:45 +00:00
|
|
|
fn action_set_property(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
2019-08-17 01:03:02 +00:00
|
|
|
let value = self.pop()?.into_number_v1() as f32;
|
2019-08-15 21:42:45 +00:00
|
|
|
let prop_index = self.pop()?.as_u32()? as usize;
|
|
|
|
let clip_path = self.pop()?;
|
|
|
|
let path = clip_path.as_string()?;
|
2019-08-17 01:30:28 +00:00
|
|
|
if let Some(clip) = Avm1::resolve_slash_path(context.active_clip, context.root, path) {
|
2019-08-15 21:42:45 +00:00
|
|
|
if let Some(clip) = clip.write(context.gc_context).as_movie_clip_mut() {
|
|
|
|
match prop_index {
|
|
|
|
0 => clip.set_x(value),
|
|
|
|
1 => clip.set_y(value),
|
|
|
|
2 => clip.set_x_scale(value),
|
|
|
|
3 => clip.set_y_scale(value),
|
2019-08-17 01:03:02 +00:00
|
|
|
10 => clip.set_rotation(value),
|
2019-08-17 01:30:28 +00:00
|
|
|
_ => log::error!(
|
|
|
|
"ActionSetProperty: Unimplemented property index {}",
|
|
|
|
prop_index
|
|
|
|
),
|
2019-08-15 21:42:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log::warn!("ActionSetProperty: Invalid path {}", path);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-15 18:29:00 +00:00
|
|
|
fn action_set_variable(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// Flash 4-style variable
|
|
|
|
let value = self.pop()?;
|
|
|
|
let var_path = self.pop()?;
|
2019-08-17 01:30:28 +00:00
|
|
|
if let Some((node, var_name)) = Self::resolve_slash_path_variable(
|
|
|
|
context.active_clip,
|
|
|
|
context.root,
|
|
|
|
var_path.as_string()?,
|
|
|
|
) {
|
2019-08-15 18:29:00 +00:00
|
|
|
if let Some(clip) = node.write(context.gc_context).as_movie_clip_mut() {
|
|
|
|
clip.set_variable(var_name, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-07-19 08:32:41 +00:00
|
|
|
fn action_set_target(
|
|
|
|
&mut self,
|
2019-08-14 19:39:26 +00:00
|
|
|
context: &mut ActionContext,
|
2019-07-19 08:32:41 +00:00
|
|
|
target: &str,
|
|
|
|
) -> Result<(), Error> {
|
2019-08-14 19:39:26 +00:00
|
|
|
if target.is_empty() {
|
2019-08-15 05:42:24 +00:00
|
|
|
context.active_clip = context.start_clip;
|
|
|
|
} else if let Some(clip) =
|
|
|
|
Avm1::resolve_slash_path(context.start_clip, context.root, target)
|
|
|
|
{
|
|
|
|
context.active_clip = clip;
|
2019-08-15 18:29:00 +00:00
|
|
|
} else {
|
|
|
|
log::warn!("SetTarget failed: {} not found", target);
|
|
|
|
// TODO: Do we change active_clip to something? Undefined?
|
2019-08-14 19:39:26 +00:00
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_stack_swap(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
self.push(a);
|
|
|
|
self.push(b);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_start_drag(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
2019-08-19 23:37:10 +00:00
|
|
|
let _target = self.pop()?;
|
|
|
|
let _lock_center = self.pop()?.as_bool();
|
|
|
|
let constrain = self.pop()?.as_bool();
|
|
|
|
if constrain {
|
|
|
|
let _y2 = self.pop()?;
|
|
|
|
let _x2 = self.pop()?;
|
|
|
|
let _y1 = self.pop()?;
|
|
|
|
let _x1 = self.pop()?;
|
|
|
|
}
|
|
|
|
log::error!("Unimplemented action: StartDrag");
|
|
|
|
Ok(())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
2019-08-14 19:39:26 +00:00
|
|
|
fn action_stop(&mut self, context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let mut display_object = context.active_clip.write(context.gc_context);
|
2019-08-19 14:53:12 +00:00
|
|
|
if let Some(clip) = display_object.as_movie_clip_mut() {
|
|
|
|
clip.stop();
|
|
|
|
} else {
|
|
|
|
log::warn!("Stop failed: Not a MovieClip");
|
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-09 23:03:13 +00:00
|
|
|
fn action_stop_sounds(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: StopSounds".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_store_register(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
_register: u8,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Does NOT pop the value from the stack.
|
|
|
|
let _val = self.stack.last().ok_or("Stack underflow")?;
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: StoreRegister".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn action_string_add(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// SWFv4 string concatenation
|
|
|
|
// TODO(Herschel): Result with non-string operands?
|
|
|
|
let a = self.pop()?.into_string();
|
|
|
|
let mut b = self.pop()?.into_string();
|
|
|
|
b.push_str(&a);
|
|
|
|
self.push(Value::String(b));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_string_equals(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// AS1 strcmp
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
let result = b.into_string() == a.into_string();
|
|
|
|
self.push(Value::from_bool_v1(result, self.swf_version));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_string_extract(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// SWFv4 substring
|
|
|
|
// TODO(Herschel): Result with incorrect operands?
|
|
|
|
let len = self.pop()?.as_f64()? as usize;
|
|
|
|
let start = self.pop()?.as_f64()? as usize;
|
|
|
|
let s = self.pop()?.into_string();
|
|
|
|
// 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>();
|
|
|
|
self.push(Value::String(result));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_string_length(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// AS1 strlen
|
|
|
|
// Only returns byte length.
|
|
|
|
// TODO(Herschel): Result with non-string operands?
|
|
|
|
let val = self.pop()?.into_string().bytes().len() as f64;
|
|
|
|
self.push(Value::Number(val));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_string_less(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// AS1 strcmp
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
// This is specifically a non-UTF8 aware comparison.
|
|
|
|
let result = b.into_string().bytes().lt(a.into_string().bytes());
|
|
|
|
self.push(Value::from_bool_v1(result, self.swf_version));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_subtract(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let a = self.pop()?;
|
|
|
|
let b = self.pop()?;
|
|
|
|
self.push(Value::Number(a.into_number_v1() + b.into_number_v1()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_target_path(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel)
|
|
|
|
let _clip = self.pop()?.as_object()?;
|
|
|
|
self.push(Value::Undefined);
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: TargetPath".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn toggle_quality(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Noop for now? Could chang anti-aliasing on render backend.
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_to_integer(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let val = self.pop()?;
|
|
|
|
self.push(Value::Number(val.into_number_v1().trunc()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_to_number(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let val = self.pop()?;
|
|
|
|
self.push(Value::Number(val.into_number()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_to_string(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let val = self.pop()?;
|
|
|
|
self.push(Value::String(val.into_string()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_trace(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let val = self.pop()?;
|
2019-08-21 00:45:54 +00:00
|
|
|
log::info!(target: "avm_trace", "{}", val.into_string());
|
2019-07-19 08:32:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_type_of(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _type_str = match self.pop()? {
|
|
|
|
Value::Undefined => "undefined",
|
|
|
|
Value::Null => "null",
|
|
|
|
Value::Number(_) => "number",
|
|
|
|
Value::Bool(_) => "boolean",
|
|
|
|
Value::String(_) => "string",
|
|
|
|
Value::Object(_) => "object",
|
|
|
|
};
|
|
|
|
// TODO(Herschel): function, movieclip
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_wait_for_frame(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
_frame: u16,
|
|
|
|
num_actions_to_skip: u8,
|
|
|
|
reader: &mut Reader<Cursor<&[u8]>>,
|
|
|
|
) -> 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.
|
|
|
|
for _ in 0..num_actions_to_skip {
|
|
|
|
reader.read_action()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_wait_for_frame_2(
|
|
|
|
&mut self,
|
|
|
|
_context: &mut ActionContext,
|
|
|
|
num_actions_to_skip: u8,
|
|
|
|
reader: &mut Reader<Cursor<&[u8]>>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// TODO(Herschel): Always true for now.
|
|
|
|
let _frame_num = self.pop()?.as_f64()? as u16;
|
|
|
|
let loaded = true;
|
|
|
|
if !loaded {
|
|
|
|
// Note that the offset is given in # of actions, NOT in bytes.
|
|
|
|
// Read the actions and toss them away.
|
|
|
|
for _ in 0..num_actions_to_skip {
|
|
|
|
reader.read_action()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn action_with(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
|
|
|
let _object = self.pop()?.as_object()?;
|
2019-08-17 01:30:28 +00:00
|
|
|
Err("Unimplemented action: With".into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ObjectPtr = std::marker::PhantomData<()>;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
#[allow(dead_code)]
|
2019-08-15 18:29:00 +00:00
|
|
|
pub enum Value {
|
2019-07-19 08:32:41 +00:00
|
|
|
Undefined,
|
|
|
|
Null,
|
|
|
|
Bool(bool),
|
|
|
|
Number(f64),
|
|
|
|
String(String),
|
|
|
|
Object(ObjectPtr),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Value {
|
|
|
|
fn into_number_v1(self) -> f64 {
|
|
|
|
match self {
|
|
|
|
Value::Bool(true) => 1.0,
|
|
|
|
Value::Number(v) => v,
|
|
|
|
Value::String(v) => v.parse().unwrap_or(0.0),
|
|
|
|
_ => 0.0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn into_number(self) -> f64 {
|
|
|
|
// ECMA-262 2nd edtion s. 9.3 ToNumber
|
|
|
|
use std::f64::NAN;
|
|
|
|
match self {
|
|
|
|
Value::Undefined => NAN,
|
|
|
|
Value::Null => NAN,
|
|
|
|
Value::Bool(false) => 0.0,
|
|
|
|
Value::Bool(true) => 1.0,
|
|
|
|
Value::Number(v) => v,
|
|
|
|
Value::String(v) => v.parse().unwrap_or(NAN), // TODO(Herschel): Handle Infinity/etc.?
|
2019-08-17 01:30:28 +00:00
|
|
|
Value::Object(_object) => {
|
|
|
|
log::error!("Unimplemented: Object ToNumber");
|
|
|
|
0.0
|
|
|
|
}
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn from_bool_v1(value: bool, swf_version: u8) -> Value {
|
|
|
|
// SWF version 4 did not have true bools and will push bools as 0 or 1.
|
|
|
|
// e.g. SWF19 p. 72:
|
|
|
|
// "If the numbers are equal, true is pushed to the stack for SWF 5 and later. For SWF 4, 1 is pushed to the stack."
|
|
|
|
if swf_version >= 5 {
|
|
|
|
Value::Bool(value)
|
|
|
|
} else {
|
|
|
|
Value::Number(if value { 1.0 } else { 0.0 })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn into_string(self) -> String {
|
|
|
|
match self {
|
|
|
|
Value::Undefined => "undefined".to_string(),
|
|
|
|
Value::Null => "null".to_string(),
|
|
|
|
Value::Bool(v) => v.to_string(),
|
|
|
|
Value::Number(v) => v.to_string(), // TODO(Herschel): Rounding for int?
|
|
|
|
Value::String(v) => v,
|
|
|
|
Value::Object(_) => "[Object object]".to_string(), // TODO(Herschel):
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_bool(&self) -> bool {
|
|
|
|
match *self {
|
|
|
|
Value::Bool(v) => v,
|
|
|
|
Value::Number(v) => v != 0.0,
|
|
|
|
// TODO(Herschel): Value::String(v) => ??
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_i32(&self) -> Result<i32, Error> {
|
|
|
|
self.as_f64().map(|n| n as i32)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_u32(&self) -> Result<u32, Error> {
|
|
|
|
self.as_f64().map(|n| n as u32)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_i64(&self) -> Result<i64, Error> {
|
|
|
|
self.as_f64().map(|n| n as i64)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_f64(&self) -> Result<f64, Error> {
|
|
|
|
match *self {
|
|
|
|
Value::Number(v) => Ok(v),
|
2019-08-26 19:04:09 +00:00
|
|
|
_ => Err(format!("Expected Number, found {:?}", self).into()),
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_string(&self) -> Result<&String, Error> {
|
|
|
|
match self {
|
|
|
|
Value::String(s) => Ok(s),
|
2019-08-26 19:04:09 +00:00
|
|
|
_ => Err(format!("Expected String, found {:?}", self).into()),
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_object(&self) -> Result<&ObjectPtr, Error> {
|
|
|
|
if let Value::Object(object) = self {
|
|
|
|
Ok(object)
|
|
|
|
} else {
|
2019-08-26 19:04:09 +00:00
|
|
|
Err(format!("Expected Object, found {:?}", self).into())
|
2019-07-19 08:32:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|