2020-07-02 03:21:30 +00:00
|
|
|
//! Whole script representation
|
|
|
|
|
|
|
|
use crate::avm2::class::Class;
|
|
|
|
use crate::avm2::function::{Avm2MethodEntry, Method};
|
2020-07-02 04:01:07 +00:00
|
|
|
use crate::avm2::r#trait::Trait;
|
2020-07-02 03:21:30 +00:00
|
|
|
use crate::avm2::Error;
|
|
|
|
use gc_arena::{Collect, GcCell, MutationContext};
|
|
|
|
use std::collections::HashMap;
|
2020-07-02 22:48:37 +00:00
|
|
|
use std::mem::drop;
|
2020-07-02 03:21:30 +00:00
|
|
|
use std::rc::Rc;
|
2020-07-02 04:01:07 +00:00
|
|
|
use swf::avm2::types::{AbcFile, Index, Script as AbcScript};
|
2020-07-02 03:21:30 +00:00
|
|
|
|
|
|
|
#[derive(Clone, Debug, Collect)]
|
|
|
|
#[collect(require_static)]
|
|
|
|
pub struct CollectWrapper<T>(T);
|
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
#[derive(Copy, Clone, Debug, Collect)]
|
2020-07-02 22:48:37 +00:00
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct TranslationUnit<'gc>(GcCell<'gc, TranslationUnitData<'gc>>);
|
|
|
|
|
2020-07-02 03:21:30 +00:00
|
|
|
/// A loaded ABC file, with any loaded ABC items alongside it.
|
|
|
|
///
|
|
|
|
/// A `TranslationUnit` is constructed when ABC loading begins, and it stores
|
|
|
|
/// all loaded ABC items (classes, methods, and scripts) as they are loaded.
|
|
|
|
/// Unit items are loaded lazily and retained in the `TranslationUnit` for
|
|
|
|
/// later retrieval.
|
|
|
|
///
|
|
|
|
/// Loaded versions of ABC items consist of the types `Class`, `Method`, and
|
|
|
|
/// `Script`, all of which correspond to their `swf` equivalents, but with
|
|
|
|
/// names preloaded. This roughly corresponds to the logical "loading" phase of
|
|
|
|
/// ABC execution as documented in the AVM2 Overview. "Linking" takes place by
|
|
|
|
/// constructing the appropriate runtime object for that item.
|
|
|
|
#[derive(Clone, Debug, Collect)]
|
|
|
|
#[collect(no_drop)]
|
2020-07-02 22:48:37 +00:00
|
|
|
pub struct TranslationUnitData<'gc> {
|
2020-07-02 03:21:30 +00:00
|
|
|
/// The ABC file that all of the following loaded data comes from.
|
|
|
|
abc: CollectWrapper<Rc<AbcFile>>,
|
|
|
|
|
|
|
|
/// All classes loaded from the ABC's class list.
|
|
|
|
classes: HashMap<u32, GcCell<'gc, Class<'gc>>>,
|
|
|
|
|
|
|
|
/// All methods loaded from the ABC's method list.
|
|
|
|
methods: HashMap<u32, Method<'gc>>,
|
|
|
|
|
|
|
|
/// All scripts loaded from the ABC's scripts list.
|
2020-07-02 04:01:07 +00:00
|
|
|
scripts: HashMap<u32, GcCell<'gc, Script<'gc>>>,
|
2020-07-02 03:21:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> TranslationUnit<'gc> {
|
2020-07-02 22:48:37 +00:00
|
|
|
pub fn from_abc(abc: Rc<AbcFile>, mc: MutationContext<'gc, '_>) -> Self {
|
|
|
|
Self(GcCell::allocate(
|
|
|
|
mc,
|
|
|
|
TranslationUnitData {
|
|
|
|
abc: CollectWrapper(abc),
|
|
|
|
classes: HashMap::new(),
|
|
|
|
methods: HashMap::new(),
|
|
|
|
scripts: HashMap::new(),
|
|
|
|
},
|
|
|
|
))
|
2020-07-02 03:21:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieve the underlying `AbcFile` for this translation unit.
|
2020-07-02 22:48:37 +00:00
|
|
|
pub fn abc(self) -> Rc<AbcFile> {
|
2020-07-02 23:51:32 +00:00
|
|
|
self.0.read().abc.0.clone()
|
2020-07-02 03:21:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Load a method from the ABC file and return it's method definition.
|
2020-07-02 22:48:37 +00:00
|
|
|
pub fn load_method(
|
|
|
|
self,
|
|
|
|
method_index: u32,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<Method<'gc>, Error> {
|
|
|
|
let write = self.0.write(mc);
|
|
|
|
if let Some(method) = write.methods.get(&method_index) {
|
2020-07-02 03:21:30 +00:00
|
|
|
return Ok(method.clone());
|
|
|
|
}
|
|
|
|
|
2020-07-02 22:48:37 +00:00
|
|
|
drop(write);
|
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
let method: Result<Avm2MethodEntry<'gc>, Error> =
|
2020-07-02 23:02:54 +00:00
|
|
|
Avm2MethodEntry::from_method_index(self, Index::new(method_index))
|
2020-07-02 03:21:30 +00:00
|
|
|
.ok_or_else(|| "Method index does not exist".into());
|
2020-07-02 23:51:32 +00:00
|
|
|
let method: Method<'gc> = method?.into();
|
2020-07-02 03:21:30 +00:00
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
self.0
|
|
|
|
.write(mc)
|
|
|
|
.methods
|
|
|
|
.insert(method_index, method.clone());
|
2020-07-02 03:21:30 +00:00
|
|
|
|
2020-07-03 01:11:10 +00:00
|
|
|
Ok(method)
|
2020-07-02 03:21:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Load a class from the ABC file and return it's class definition.
|
|
|
|
pub fn load_class(
|
2020-07-02 22:48:37 +00:00
|
|
|
self,
|
2020-07-02 03:21:30 +00:00
|
|
|
class_index: u32,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<GcCell<'gc, Class<'gc>>, Error> {
|
2020-07-02 22:48:37 +00:00
|
|
|
let write = self.0.write(mc);
|
|
|
|
if let Some(class) = write.classes.get(&class_index) {
|
2020-07-03 01:11:10 +00:00
|
|
|
return Ok(*class);
|
2020-07-02 03:21:30 +00:00
|
|
|
}
|
|
|
|
|
2020-07-02 22:48:37 +00:00
|
|
|
drop(write);
|
2020-07-02 03:21:30 +00:00
|
|
|
|
2020-07-02 22:48:37 +00:00
|
|
|
let class = Class::from_abc_index(self, class_index, mc)?;
|
|
|
|
self.0.write(mc).classes.insert(class_index, class);
|
|
|
|
|
|
|
|
class.write(mc).load_traits(self, class_index, mc)?;
|
2020-07-02 03:21:30 +00:00
|
|
|
|
2020-07-03 01:11:10 +00:00
|
|
|
Ok(class)
|
2020-07-02 03:21:30 +00:00
|
|
|
}
|
2020-07-02 04:01:07 +00:00
|
|
|
|
|
|
|
/// Load a script from the ABC file and return it's script definition.
|
|
|
|
pub fn load_script(
|
2020-07-02 22:48:37 +00:00
|
|
|
self,
|
2020-07-02 04:01:07 +00:00
|
|
|
script_index: u32,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<GcCell<'gc, Script<'gc>>, Error> {
|
2020-07-02 22:48:37 +00:00
|
|
|
let write = self.0.write(mc);
|
|
|
|
if let Some(scripts) = write.scripts.get(&script_index) {
|
2020-07-03 01:11:10 +00:00
|
|
|
return Ok(*scripts);
|
2020-07-02 04:01:07 +00:00
|
|
|
}
|
|
|
|
|
2020-07-02 22:48:37 +00:00
|
|
|
drop(write);
|
|
|
|
|
|
|
|
let script = Script::from_abc_index(self, script_index, mc)?;
|
|
|
|
self.0.write(mc).scripts.insert(script_index, script);
|
2020-07-02 04:01:07 +00:00
|
|
|
|
2020-07-02 22:48:37 +00:00
|
|
|
script.write(mc).load_traits(self, script_index, mc)?;
|
2020-07-02 04:01:07 +00:00
|
|
|
|
2020-07-03 01:11:10 +00:00
|
|
|
Ok(script)
|
2020-07-02 04:01:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A loaded Script from an ABC file.
|
|
|
|
#[derive(Clone, Debug, Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct Script<'gc> {
|
|
|
|
/// The initializer method to run for the script.
|
|
|
|
init: Method<'gc>,
|
|
|
|
|
|
|
|
/// Traits that this script uses.
|
|
|
|
traits: Vec<Trait<'gc>>,
|
|
|
|
|
|
|
|
/// Whether or not we loaded our traits.
|
|
|
|
traits_loaded: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Script<'gc> {
|
|
|
|
/// Construct a script from a `TranslationUnit` and it's script index.
|
|
|
|
///
|
|
|
|
/// The returned script will be allocated, but no traits will be loaded.
|
|
|
|
/// The caller is responsible for storing the class in the
|
|
|
|
/// `TranslationUnit` and calling `load_traits` to complete the
|
|
|
|
/// trait-loading process.
|
|
|
|
pub fn from_abc_index(
|
2020-07-02 22:48:37 +00:00
|
|
|
unit: TranslationUnit<'gc>,
|
2020-07-02 04:01:07 +00:00
|
|
|
script_index: u32,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<GcCell<'gc, Self>, Error> {
|
2020-07-02 23:51:32 +00:00
|
|
|
let abc = unit.abc();
|
|
|
|
let script: Result<&AbcScript, Error> = abc
|
2020-07-02 04:01:07 +00:00
|
|
|
.scripts
|
|
|
|
.get(script_index as usize)
|
|
|
|
.ok_or_else(|| "LoadError: Script index not valid".into());
|
|
|
|
let script = script?;
|
|
|
|
|
2020-07-02 22:48:37 +00:00
|
|
|
let init = unit.load_method(script.init_method.0, mc)?;
|
2020-07-02 04:01:07 +00:00
|
|
|
|
|
|
|
Ok(GcCell::allocate(
|
|
|
|
mc,
|
|
|
|
Self {
|
|
|
|
init,
|
|
|
|
traits: Vec::new(),
|
|
|
|
traits_loaded: false,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Finish the class-loading process by loading traits.
|
|
|
|
///
|
|
|
|
/// This process must be done after the `Script` has been stored in the
|
|
|
|
/// `TranslationUnit`. Failing to do so runs the risk of runaway recursion
|
|
|
|
/// or double-borrows. It should be done before the script is actually
|
|
|
|
/// executed.
|
|
|
|
pub fn load_traits(
|
|
|
|
&mut self,
|
2020-07-02 22:48:37 +00:00
|
|
|
unit: TranslationUnit<'gc>,
|
2020-07-02 04:01:07 +00:00
|
|
|
script_index: u32,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
if self.traits_loaded {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
self.traits_loaded = true;
|
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
let abc = unit.abc();
|
|
|
|
let script: Result<&AbcScript, Error> = abc
|
2020-07-02 04:01:07 +00:00
|
|
|
.scripts
|
|
|
|
.get(script_index as usize)
|
|
|
|
.ok_or_else(|| "LoadError: Script index not valid".into());
|
|
|
|
let script = script?;
|
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
for abc_trait in script.traits.iter() {
|
2020-07-02 04:01:07 +00:00
|
|
|
self.traits
|
|
|
|
.push(Trait::from_abc_trait(unit, &abc_trait, mc)?);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the entrypoint for the script.
|
|
|
|
pub fn init(&self) -> Method<'gc> {
|
2020-07-02 23:51:32 +00:00
|
|
|
self.init.clone()
|
2020-07-02 04:01:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Return traits for this script.
|
|
|
|
///
|
|
|
|
/// This function will return an error if it is incorrectly called before
|
|
|
|
/// traits are loaded.
|
|
|
|
pub fn traits(&self) -> Result<&[Trait<'gc>], Error> {
|
|
|
|
if !self.traits_loaded {
|
|
|
|
return Err("LoadError: Script traits accessed before they were loaded!".into());
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(&self.traits)
|
|
|
|
}
|
2020-07-02 03:21:30 +00:00
|
|
|
}
|