avm1: Implement GetVariable/SetVariable

This commit is contained in:
Mike Welsh 2019-08-15 11:29:00 -07:00
parent ecd9b18e90
commit 20ec170552
4 changed files with 78 additions and 25 deletions

View File

@ -75,6 +75,7 @@ impl Avm1 {
Action::GetMember => self.action_get_member(context)?, Action::GetMember => self.action_get_member(context)?,
Action::GetProperty => self.action_get_property(context)?, Action::GetProperty => self.action_get_property(context)?,
Action::GetTime => self.action_get_time(context)?, Action::GetTime => self.action_get_time(context)?,
Action::GetVariable => self.action_get_variable(context)?,
Action::GetUrl { url, target } => self.action_get_url(context, &url, &target)?, Action::GetUrl { url, target } => self.action_get_url(context, &url, &target)?,
Action::GetUrl2 { Action::GetUrl2 {
send_vars_method, send_vars_method,
@ -120,6 +121,7 @@ impl Avm1 {
Action::Return => self.action_return(context)?, Action::Return => self.action_return(context)?,
Action::SetMember => self.action_set_member(context)?, Action::SetMember => self.action_set_member(context)?,
Action::SetTarget(target) => self.action_set_target(context, &target)?, Action::SetTarget(target) => self.action_set_target(context, &target)?,
Action::SetVariable => self.action_set_variable(context)?,
Action::StackSwap => self.action_stack_swap(context)?, Action::StackSwap => self.action_stack_swap(context)?,
Action::StartDrag => self.action_start_drag(context)?, Action::StartDrag => self.action_start_drag(context)?,
Action::Stop => self.action_stop(context)?, Action::Stop => self.action_stop(context)?,
@ -183,6 +185,25 @@ impl Avm1 {
Some(cur_clip) Some(cur_clip)
} }
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)),
(Some(path), Some(var_name)) => if let Some(node) = Self::resolve_slash_path(start, root, path) {
return Some((node, var_name));
}
_ => (),
}
}
None
}
fn push(&mut self, value: impl Into<Value>) { fn push(&mut self, value: impl Into<Value>) {
self.stack.push(value.into()); self.stack.push(value.into());
} }
@ -476,6 +497,19 @@ impl Avm1 {
Ok(()) Ok(())
} }
fn action_get_variable(&mut self, context: &mut ActionContext) -> Result<(), Error> {
// Flash 4-style variable
let var_path = self.pop()?;
if let Some((node, var_name)) = Self::resolve_slash_path_variable(context.active_clip, context.root, var_path.as_string()?) {
if let Some(clip) = node.read().as_movie_clip() {
self.push(clip.get_variable(var_name));
}
} else {
self.push(Value::Undefined);
}
Ok(())
}
fn action_get_url( fn action_get_url(
&mut self, &mut self,
_context: &mut ActionContext, _context: &mut ActionContext,
@ -504,11 +538,7 @@ impl Avm1 {
fn action_goto_frame(&mut self, context: &mut ActionContext, frame: u16) -> Result<(), Error> { 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 mut display_object = context.active_clip.write(context.gc_context);
let clip = display_object.as_movie_clip_mut().unwrap(); let clip = display_object.as_movie_clip_mut().unwrap();
if clip.playing() { clip.goto_frame(frame + 1, true);
clip.goto_frame(frame + 1, false);
} else {
clip.goto_frame(frame + 1, true);
}
Ok(()) Ok(())
} }
@ -798,6 +828,18 @@ impl Avm1 {
unimplemented!("Action::SetMember"); unimplemented!("Action::SetMember");
} }
fn action_set_variable(&mut self, context: &mut ActionContext) -> Result<(), Error> {
// Flash 4-style variable
let value = self.pop()?;
let var_path = self.pop()?;
if let Some((node, var_name)) = Self::resolve_slash_path_variable(context.active_clip, context.root, var_path.as_string()?) {
if let Some(clip) = node.write(context.gc_context).as_movie_clip_mut() {
clip.set_variable(var_name, value);
}
}
Ok(())
}
fn action_set_target( fn action_set_target(
&mut self, &mut self,
context: &mut ActionContext, context: &mut ActionContext,
@ -809,6 +851,9 @@ impl Avm1 {
Avm1::resolve_slash_path(context.start_clip, context.root, target) Avm1::resolve_slash_path(context.start_clip, context.root, target)
{ {
context.active_clip = clip; context.active_clip = clip;
} else {
log::warn!("SetTarget failed: {} not found", target);
// TODO: Do we change active_clip to something? Undefined?
} }
Ok(()) Ok(())
} }
@ -1008,7 +1053,7 @@ type ObjectPtr = std::marker::PhantomData<()>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[allow(dead_code)] #[allow(dead_code)]
enum Value { pub enum Value {
Undefined, Undefined,
Null, Null,
Bool(bool), Bool(bool),

View File

@ -52,7 +52,9 @@ impl<'gc> Button<'gc> {
for actions in &button.actions { for actions in &button.actions {
if actions if actions
.conditions .conditions
.contains(&swf::ButtonActionCondition::OverDownToOverUp) .contains(&swf::ButtonActionCondition::OverDownToOverUp) || actions
.conditions
.contains(&swf::ButtonActionCondition::OverUpToOverDown)
{ {
release_actions = actions.action_data.clone(); release_actions = actions.action_data.clone();
} }
@ -123,7 +125,7 @@ impl<'gc> DisplayObject<'gc> for Button<'gc> {
fn hit_test(&self, point: (Twips, Twips)) -> bool { fn hit_test(&self, point: (Twips, Twips)) -> bool {
//if self.world_bounds().contains(point) { //if self.world_bounds().contains(point) {
for child in self.children_in_state(self.state).rev() { for child in self.children_in_state(self.state).rev() {
if child.read().hit_test(point) { if child.read().world_bounds().contains(point) {
return true; return true;
} }
} }

View File

@ -41,10 +41,6 @@ impl<'gc> DisplayObject<'gc> for Graphic<'gc> {
bounds bounds
} }
fn hit_test(&self, point: (Twips, Twips)) -> bool {
self.world_bounds().contains(point)
}
fn run_frame(&mut self, _context: &mut UpdateContext) { fn run_frame(&mut self, _context: &mut UpdateContext) {
// Noop // Noop
} }

View File

@ -1,3 +1,4 @@
use crate::avm1;
use crate::backend::audio::AudioStreamHandle; use crate::backend::audio::AudioStreamHandle;
use crate::character::Character; use crate::character::Character;
use crate::color_transform::ColorTransform; use crate::color_transform::ColorTransform;
@ -10,7 +11,7 @@ use crate::player::{RenderContext, UpdateContext};
use crate::prelude::*; use crate::prelude::*;
use crate::tag_utils::{self, DecodeResult, SwfStream}; use crate::tag_utils::{self, DecodeResult, SwfStream};
use crate::text::Text; use crate::text::Text;
use std::collections::BTreeMap; use std::collections::{BTreeMap, HashMap};
use swf::read::SwfRead; use swf::read::SwfRead;
type Depth = i16; type Depth = i16;
@ -32,6 +33,7 @@ pub struct MovieClip<'gc> {
audio_stream: Option<AudioStreamHandle>, audio_stream: Option<AudioStreamHandle>,
children: BTreeMap<Depth, DisplayNode<'gc>>, children: BTreeMap<Depth, DisplayNode<'gc>>,
variables: HashMap<String, avm1::Value>,
} }
impl<'gc> MovieClip<'gc> { impl<'gc> MovieClip<'gc> {
@ -49,6 +51,7 @@ impl<'gc> MovieClip<'gc> {
audio_stream: None, audio_stream: None,
audio_stream_info: None, audio_stream_info: None,
children: BTreeMap::new(), children: BTreeMap::new(),
variables: HashMap::new(),
} }
} }
@ -71,6 +74,7 @@ impl<'gc> MovieClip<'gc> {
audio_stream_info: None, audio_stream_info: None,
total_frames: num_frames, total_frames: num_frames,
children: BTreeMap::new(), children: BTreeMap::new(),
variables: HashMap::new(),
} }
} }
@ -99,7 +103,9 @@ impl<'gc> MovieClip<'gc> {
} }
pub fn goto_frame(&mut self, frame: FrameNumber, stop: bool) { pub fn goto_frame(&mut self, frame: FrameNumber, stop: bool) {
self.goto_queue.push(frame); if frame != self.current_frame {
self.goto_queue.push(frame);
}
if stop { if stop {
self.stop(); self.stop();
@ -138,6 +144,7 @@ impl<'gc> MovieClip<'gc> {
} }
pub fn get_child_by_name(&self, name: &str) -> Option<&DisplayNode<'gc>> { pub fn get_child_by_name(&self, name: &str) -> Option<&DisplayNode<'gc>> {
// TODO: Make a HashMap from name -> child?
self.children self.children
.values() .values()
.find(|child| child.read().name() == name) .find(|child| child.read().name() == name)
@ -195,6 +202,16 @@ impl<'gc> MovieClip<'gc> {
self.goto_queue.clear(); self.goto_queue.clear();
} }
pub fn get_variable(&self, var_name: &str) -> avm1::Value {
// TODO: Value should be Copy (and contain a Cow/GcCell for big objects)
self.variables.get(var_name).unwrap_or(&avm1::Value::Undefined).clone()
}
pub fn set_variable(&mut self, var_name: &str, value: avm1::Value) {
// TODO: Cow for String values.
self.variables.insert(var_name.to_owned(), value);
}
fn reader<'a>( fn reader<'a>(
&self, &self,
context: &UpdateContext<'a, '_, '_>, context: &UpdateContext<'a, '_, '_>,
@ -342,22 +359,15 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
if child.read().hit_test(point) { if child.read().hit_test(point) {
return Some(*child); return Some(*child);
} }
}
//}
None let button = child.read().pick(point);
} if button.is_some() {
return button;
fn hit_test(&self, point: (Twips, Twips)) -> bool {
//if self.world_bounds().contains(point) {
for child in self.children.values().rev() {
if child.read().hit_test(point) {
return true;
} }
} }
//} //}
false None
} }
fn as_movie_clip(&self) -> Option<&crate::movie_clip::MovieClip<'gc>> { fn as_movie_clip(&self) -> Option<&crate::movie_clip::MovieClip<'gc>> {