-
-
Notifications
You must be signed in to change notification settings - Fork 790
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
avm2: Initial AVM2 interpreter (merge #404)
Initial work on the AVM2 interpreter.
- Loading branch information
Showing
175 changed files
with
9,543 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
//! ActionScript Virtual Machine 2 (AS3) support | ||
|
||
use crate::avm2::activation::Activation; | ||
use crate::avm2::globals::SystemPrototypes; | ||
use crate::avm2::object::{Object, TObject}; | ||
use crate::avm2::scope::Scope; | ||
use crate::avm2::script::Script; | ||
use crate::avm2::script::TranslationUnit; | ||
use crate::avm2::value::Value; | ||
use crate::context::UpdateContext; | ||
use crate::tag_utils::SwfSlice; | ||
use gc_arena::{Collect, GcCell, MutationContext}; | ||
use std::rc::Rc; | ||
use swf::avm2::read::Reader; | ||
|
||
#[macro_export] | ||
macro_rules! avm_debug { | ||
($($arg:tt)*) => ( | ||
#[cfg(feature = "avm_debug")] | ||
log::debug!($($arg)*) | ||
) | ||
} | ||
|
||
mod activation; | ||
mod class; | ||
mod function; | ||
mod globals; | ||
mod method; | ||
mod names; | ||
mod object; | ||
mod property; | ||
mod property_map; | ||
mod return_value; | ||
mod scope; | ||
mod script; | ||
mod script_object; | ||
mod slot; | ||
mod string; | ||
mod r#trait; | ||
mod value; | ||
|
||
/// Boxed error alias. | ||
/// | ||
/// As AVM2 is a far stricter VM than AVM1, this may eventually be replaced | ||
/// with a proper Avm2Error enum. | ||
type Error = Box<dyn std::error::Error>; | ||
|
||
/// The state of an AVM2 interpreter. | ||
#[derive(Collect)] | ||
#[collect(no_drop)] | ||
pub struct Avm2<'gc> { | ||
/// Values currently present on the operand stack. | ||
stack: Vec<Value<'gc>>, | ||
|
||
/// Global scope object. | ||
globals: Object<'gc>, | ||
|
||
/// System prototypes. | ||
system_prototypes: SystemPrototypes<'gc>, | ||
} | ||
|
||
impl<'gc> Avm2<'gc> { | ||
/// Construct a new AVM interpreter. | ||
pub fn new(mc: MutationContext<'gc, '_>) -> Self { | ||
let (globals, system_prototypes) = globals::construct_global_scope(mc); | ||
|
||
Self { | ||
stack: Vec::new(), | ||
globals, | ||
system_prototypes, | ||
} | ||
} | ||
|
||
/// Return the current set of system prototypes. | ||
pub fn prototypes(&self) -> &SystemPrototypes<'gc> { | ||
&self.system_prototypes | ||
} | ||
|
||
/// Run a script's initializer method. | ||
pub fn run_script_initializer( | ||
&mut self, | ||
script: GcCell<'gc, Script<'gc>>, | ||
context: &mut UpdateContext<'_, 'gc, '_>, | ||
) -> Result<(), Error> { | ||
let mut init_activation = Activation::from_script(self, context, script, self.globals)?; | ||
|
||
init_activation.run_stack_frame_for_script(context, script) | ||
} | ||
|
||
/// Load an ABC file embedded in a `SwfSlice`. | ||
/// | ||
/// The `SwfSlice` must resolve to the contents of an ABC file. | ||
pub fn load_abc( | ||
&mut self, | ||
abc: SwfSlice, | ||
_abc_name: &str, | ||
_lazy_init: bool, | ||
context: &mut UpdateContext<'_, 'gc, '_>, | ||
) -> Result<(), Error> { | ||
let mut read = Reader::new(abc.as_ref()); | ||
|
||
let abc_file = Rc::new(read.read()?); | ||
let tunit = TranslationUnit::from_abc(abc_file.clone(), context.gc_context); | ||
|
||
for i in (0..abc_file.scripts.len()).rev() { | ||
let script = tunit.load_script(i as u32, context.gc_context)?; | ||
let mut globals = self.globals(); | ||
let scope = Scope::push_scope(None, globals, context.gc_context); | ||
let mut null_activation = Activation::from_nothing(self, context); | ||
|
||
// TODO: Lazyinit means we shouldn't do this until traits are | ||
// actually mentioned... | ||
for trait_entry in script.read().traits()?.iter() { | ||
globals.install_foreign_trait( | ||
&mut null_activation, | ||
context, | ||
trait_entry.clone(), | ||
Some(scope), | ||
globals, | ||
)?; | ||
} | ||
|
||
drop(null_activation); | ||
|
||
self.run_script_initializer(script, context)?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn globals(&self) -> Object<'gc> { | ||
self.globals | ||
} | ||
|
||
/// Push a value onto the operand stack. | ||
fn push(&mut self, value: impl Into<Value<'gc>>) { | ||
let value = value.into(); | ||
avm_debug!("Stack push {}: {:?}", self.stack.len(), value); | ||
self.stack.push(value); | ||
} | ||
|
||
/// Retrieve the top-most value on the operand stack. | ||
#[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 | ||
} | ||
|
||
fn pop_args(&mut self, arg_count: u32) -> Vec<Value<'gc>> { | ||
let mut args = Vec::with_capacity(arg_count as usize); | ||
args.resize(arg_count as usize, Value::Undefined); | ||
for arg in args.iter_mut().rev() { | ||
*arg = self.pop(); | ||
} | ||
|
||
args | ||
} | ||
} |
Oops, something went wrong.