2022-08-19 14:14:37 +00:00
|
|
|
use crate::avm1::TObject as _;
|
2020-08-27 22:16:53 +00:00
|
|
|
use crate::avm1::Value as Avm1Value;
|
2022-08-19 14:14:37 +00:00
|
|
|
use crate::avm1::{Activation as Avm1Activation, ActivationIdentifier as Avm1ActivationIdentifier};
|
2020-08-28 15:24:26 +00:00
|
|
|
use crate::avm1::{
|
2021-09-12 10:20:51 +00:00
|
|
|
ArrayObject as Avm1ArrayObject, Error as Avm1Error, Object as Avm1Object,
|
|
|
|
ScriptObject as Avm1ScriptObject,
|
2020-08-28 15:24:26 +00:00
|
|
|
};
|
2022-03-07 20:03:04 +00:00
|
|
|
use crate::avm2::activation::Activation as Avm2Activation;
|
|
|
|
use crate::avm2::object::TObject as _;
|
|
|
|
use crate::avm2::Value as Avm2Value;
|
2022-09-02 08:43:33 +00:00
|
|
|
use crate::avm2::{ArrayObject as Avm2ArrayObject, Object as Avm2Object};
|
2020-08-28 10:24:19 +00:00
|
|
|
use crate::context::UpdateContext;
|
2021-09-12 10:20:51 +00:00
|
|
|
use crate::string::AvmString;
|
2021-02-18 02:38:55 +00:00
|
|
|
use gc_arena::Collect;
|
2020-08-28 15:24:26 +00:00
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
|
|
|
/// An intermediate format of representing shared data between ActionScript and elsewhere.
|
|
|
|
/// Regardless of the capabilities of both sides, all data will be translated to this potentially
|
|
|
|
/// lossy format. Any recursion or additional metadata in ActionScript will not be translated.
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
pub enum Value {
|
|
|
|
Null,
|
|
|
|
Bool(bool),
|
|
|
|
Number(f64),
|
|
|
|
String(String),
|
|
|
|
Object(BTreeMap<String, Value>),
|
|
|
|
List(Vec<Value>),
|
|
|
|
}
|
|
|
|
|
2021-09-12 10:20:51 +00:00
|
|
|
impl From<AvmString<'_>> for Value {
|
|
|
|
fn from(string: AvmString<'_>) -> Self {
|
2020-08-28 15:24:26 +00:00
|
|
|
Value::String(string.to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<String> for Value {
|
|
|
|
fn from(string: String) -> Self {
|
|
|
|
Value::String(string)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&'static str> for Value {
|
|
|
|
fn from(string: &'static str) -> Self {
|
|
|
|
Value::String(string.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<bool> for Value {
|
|
|
|
fn from(value: bool) -> Self {
|
|
|
|
Value::Bool(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<f64> for Value {
|
|
|
|
fn from(value: f64) -> Self {
|
|
|
|
Value::Number(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<f32> for Value {
|
|
|
|
fn from(value: f32) -> Self {
|
|
|
|
Value::Number(f64::from(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<u8> for Value {
|
|
|
|
fn from(value: u8) -> Self {
|
|
|
|
Value::Number(f64::from(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<i16> for Value {
|
|
|
|
fn from(value: i16) -> Self {
|
|
|
|
Value::Number(f64::from(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<u16> for Value {
|
|
|
|
fn from(value: u16) -> Self {
|
|
|
|
Value::Number(f64::from(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<i32> for Value {
|
|
|
|
fn from(value: i32) -> Self {
|
|
|
|
Value::Number(f64::from(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<i64> for Value {
|
|
|
|
fn from(value: i64) -> Self {
|
|
|
|
Value::Number(value as f64)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<u32> for Value {
|
|
|
|
fn from(value: u32) -> Self {
|
|
|
|
Value::Number(f64::from(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<usize> for Value {
|
|
|
|
fn from(value: usize) -> Self {
|
|
|
|
Value::Number(value as f64)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<BTreeMap<String, Value>> for Value {
|
|
|
|
fn from(value: BTreeMap<String, Value>) -> Self {
|
|
|
|
Value::Object(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Vec<Value>> for Value {
|
|
|
|
fn from(value: Vec<Value>) -> Self {
|
|
|
|
Value::List(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Value {
|
|
|
|
pub fn from_avm1<'gc>(
|
2023-01-06 23:33:31 +00:00
|
|
|
activation: &mut Avm1Activation<'_, 'gc>,
|
2020-08-28 15:24:26 +00:00
|
|
|
value: Avm1Value<'gc>,
|
2021-06-18 14:24:05 +00:00
|
|
|
) -> Result<Value, Avm1Error<'gc>> {
|
2020-08-28 15:24:26 +00:00
|
|
|
Ok(match value {
|
|
|
|
Avm1Value::Undefined | Avm1Value::Null => Value::Null,
|
2021-06-22 17:26:54 +00:00
|
|
|
Avm1Value::Bool(value) => value.into(),
|
|
|
|
Avm1Value::Number(value) => value.into(),
|
2020-08-28 15:24:26 +00:00
|
|
|
Avm1Value::String(value) => Value::String(value.to_string()),
|
2023-02-13 01:13:38 +00:00
|
|
|
Avm1Value::MovieClip(_) => todo!(),
|
2020-08-28 15:24:26 +00:00
|
|
|
Avm1Value::Object(object) => {
|
2021-06-18 14:24:05 +00:00
|
|
|
if object.as_array_object().is_some() {
|
2021-06-06 00:25:21 +00:00
|
|
|
let length = object.length(activation)?;
|
2021-06-18 14:24:05 +00:00
|
|
|
let values: Result<Vec<_>, Avm1Error<'gc>> = (0..length)
|
2021-06-06 00:25:21 +00:00
|
|
|
.map(|i| {
|
|
|
|
let element = object.get_element(activation, i);
|
|
|
|
Value::from_avm1(activation, element)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
Value::List(values?)
|
2020-08-28 15:24:26 +00:00
|
|
|
} else {
|
|
|
|
let keys = object.get_keys(activation);
|
|
|
|
let mut values = BTreeMap::new();
|
|
|
|
for key in keys {
|
2021-05-09 10:14:54 +00:00
|
|
|
let value = object.get(key, activation)?;
|
|
|
|
values.insert(key.to_string(), Value::from_avm1(activation, value)?);
|
2020-08-28 15:24:26 +00:00
|
|
|
}
|
|
|
|
Value::Object(values)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-01-06 23:33:31 +00:00
|
|
|
pub fn into_avm1<'gc>(self, activation: &mut Avm1Activation<'_, 'gc>) -> Avm1Value<'gc> {
|
2020-08-28 15:24:26 +00:00
|
|
|
match self {
|
|
|
|
Value::Null => Avm1Value::Null,
|
|
|
|
Value::Bool(value) => Avm1Value::Bool(value),
|
|
|
|
Value::Number(value) => Avm1Value::Number(value),
|
|
|
|
Value::String(value) => {
|
2022-11-22 20:50:30 +00:00
|
|
|
let value = if activation.swf_version() < 9 && value.trim().is_empty() {
|
|
|
|
"null"
|
|
|
|
} else {
|
|
|
|
&value
|
|
|
|
};
|
2021-10-05 21:12:41 +00:00
|
|
|
Avm1Value::String(AvmString::new_utf8(activation.context.gc_context, value))
|
2020-08-28 15:24:26 +00:00
|
|
|
}
|
|
|
|
Value::Object(values) => {
|
2022-08-19 10:50:44 +00:00
|
|
|
let object = Avm1ScriptObject::new(
|
2020-08-28 15:24:26 +00:00
|
|
|
activation.context.gc_context,
|
|
|
|
Some(activation.context.avm1.prototypes().object),
|
|
|
|
);
|
|
|
|
for (key, value) in values {
|
2021-10-05 21:12:41 +00:00
|
|
|
let key = AvmString::new_utf8(activation.context.gc_context, key);
|
2021-05-09 10:14:54 +00:00
|
|
|
let _ = object.set(key, value.into_avm1(activation), activation);
|
2020-08-28 15:24:26 +00:00
|
|
|
}
|
|
|
|
object.into()
|
|
|
|
}
|
2021-06-18 14:24:05 +00:00
|
|
|
Value::List(values) => Avm1ArrayObject::new(
|
|
|
|
activation.context.gc_context,
|
|
|
|
activation.context.avm1.prototypes().array,
|
|
|
|
values
|
|
|
|
.iter()
|
|
|
|
.map(|value| value.to_owned().into_avm1(activation)),
|
|
|
|
)
|
|
|
|
.into(),
|
2020-08-28 15:24:26 +00:00
|
|
|
}
|
|
|
|
}
|
2022-03-07 20:03:04 +00:00
|
|
|
|
2022-09-02 08:43:33 +00:00
|
|
|
pub fn from_avm2(value: Avm2Value) -> Value {
|
|
|
|
match value {
|
2022-03-07 20:03:04 +00:00
|
|
|
Avm2Value::Undefined | Avm2Value::Null => Value::Null,
|
|
|
|
Avm2Value::Bool(value) => value.into(),
|
|
|
|
Avm2Value::Number(value) => value.into(),
|
|
|
|
Avm2Value::Integer(value) => value.into(),
|
|
|
|
Avm2Value::String(value) => Value::String(value.to_string()),
|
|
|
|
Avm2Value::Object(object) => {
|
|
|
|
if let Some(array) = object.as_array_storage() {
|
|
|
|
let length = array.length();
|
2022-09-02 08:43:33 +00:00
|
|
|
let values = (0..length)
|
2022-03-07 20:03:04 +00:00
|
|
|
.map(|i| {
|
|
|
|
// FIXME - is this right?
|
|
|
|
let element = array.get(i).unwrap_or(Avm2Value::Null);
|
2022-09-02 08:40:18 +00:00
|
|
|
Value::from_avm2(element)
|
2022-03-07 20:03:04 +00:00
|
|
|
})
|
|
|
|
.collect();
|
2022-09-02 08:43:33 +00:00
|
|
|
Value::List(values)
|
2022-03-07 20:03:04 +00:00
|
|
|
} else {
|
2023-01-04 10:09:47 +00:00
|
|
|
tracing::warn!("from_avm2 needs to be implemented for Avm2Value::Object");
|
2022-03-07 20:03:04 +00:00
|
|
|
Value::Null
|
|
|
|
}
|
|
|
|
}
|
2022-09-02 08:43:33 +00:00
|
|
|
}
|
2022-03-07 20:03:04 +00:00
|
|
|
}
|
|
|
|
|
2023-01-06 23:33:31 +00:00
|
|
|
pub fn into_avm2<'gc>(self, activation: &mut Avm2Activation<'_, 'gc>) -> Avm2Value<'gc> {
|
2022-03-07 20:03:04 +00:00
|
|
|
match self {
|
|
|
|
Value::Null => Avm2Value::Null,
|
|
|
|
Value::Bool(value) => Avm2Value::Bool(value),
|
|
|
|
Value::Number(value) => Avm2Value::Number(value),
|
|
|
|
Value::String(value) => {
|
|
|
|
Avm2Value::String(AvmString::new_utf8(activation.context.gc_context, value))
|
|
|
|
}
|
|
|
|
Value::Object(_values) => {
|
2023-01-04 10:09:47 +00:00
|
|
|
tracing::warn!("into_avm2 needs to be implemented for Value::Object");
|
2022-03-07 20:03:04 +00:00
|
|
|
Avm2Value::Undefined
|
|
|
|
}
|
|
|
|
Value::List(values) => {
|
|
|
|
let storage = values
|
|
|
|
.iter()
|
|
|
|
.map(|value| value.to_owned().into_avm2(activation))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
Avm2Value::Object(Avm2ArrayObject::from_storage(activation, storage).unwrap())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-28 15:24:26 +00:00
|
|
|
}
|
2020-08-27 22:16:53 +00:00
|
|
|
|
2020-08-28 10:24:19 +00:00
|
|
|
#[derive(Collect, Clone)]
|
2020-08-27 22:16:53 +00:00
|
|
|
#[collect(no_drop)]
|
|
|
|
pub enum Callback<'gc> {
|
|
|
|
Avm1 {
|
|
|
|
this: Avm1Value<'gc>,
|
|
|
|
method: Avm1Object<'gc>,
|
|
|
|
},
|
2022-03-07 20:03:04 +00:00
|
|
|
Avm2 {
|
|
|
|
method: Avm2Object<'gc>,
|
|
|
|
},
|
2020-08-27 22:16:53 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 10:24:19 +00:00
|
|
|
impl<'gc> Callback<'gc> {
|
2020-08-28 15:24:26 +00:00
|
|
|
pub fn call(
|
|
|
|
&self,
|
2023-01-06 23:33:31 +00:00
|
|
|
context: &mut UpdateContext<'_, 'gc>,
|
2020-08-28 15:24:26 +00:00
|
|
|
name: &str,
|
|
|
|
args: impl IntoIterator<Item = Value>,
|
|
|
|
) -> Value {
|
2020-08-28 10:24:19 +00:00
|
|
|
match self {
|
|
|
|
Callback::Avm1 { this, method } => {
|
2021-04-17 19:38:11 +00:00
|
|
|
let base_clip = context.stage.root_clip();
|
2020-08-28 15:24:26 +00:00
|
|
|
let mut activation = Avm1Activation::from_nothing(
|
2020-08-28 10:24:19 +00:00
|
|
|
context.reborrow(),
|
2020-08-28 15:24:26 +00:00
|
|
|
Avm1ActivationIdentifier::root("[ExternalInterface]"),
|
2020-08-28 10:24:19 +00:00
|
|
|
base_clip,
|
|
|
|
);
|
|
|
|
let this = this.coerce_to_object(&mut activation);
|
2020-08-28 15:24:26 +00:00
|
|
|
let args: Vec<Avm1Value> = args
|
|
|
|
.into_iter()
|
|
|
|
.map(|v| v.into_avm1(&mut activation))
|
|
|
|
.collect();
|
2021-10-05 21:12:41 +00:00
|
|
|
let name = AvmString::new_utf8(activation.context.gc_context, name);
|
2020-08-28 15:24:26 +00:00
|
|
|
if let Ok(result) = method
|
2022-01-19 22:48:32 +00:00
|
|
|
.call(name, &mut activation, this.into(), &args)
|
2020-08-28 15:24:26 +00:00
|
|
|
.and_then(|value| Value::from_avm1(&mut activation, value))
|
|
|
|
{
|
|
|
|
result
|
|
|
|
} else {
|
|
|
|
Value::Null
|
|
|
|
}
|
2022-03-07 20:03:04 +00:00
|
|
|
}
|
|
|
|
Callback::Avm2 { method } => {
|
|
|
|
let mut activation = Avm2Activation::from_nothing(context.reborrow());
|
|
|
|
let args: Vec<Avm2Value> = args
|
|
|
|
.into_iter()
|
|
|
|
.map(|v| v.into_avm2(&mut activation))
|
|
|
|
.collect();
|
2022-09-02 08:43:33 +00:00
|
|
|
if let Ok(result) = method.call(None, &args, &mut activation) {
|
|
|
|
Value::from_avm2(result)
|
2022-03-07 20:03:04 +00:00
|
|
|
} else {
|
|
|
|
Value::Null
|
|
|
|
}
|
2020-08-28 10:24:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-28 10:43:21 +00:00
|
|
|
pub trait ExternalInterfaceProvider {
|
2020-08-31 21:32:12 +00:00
|
|
|
fn get_method(&self, name: &str) -> Option<Box<dyn ExternalInterfaceMethod>>;
|
2020-09-02 19:02:32 +00:00
|
|
|
|
|
|
|
fn on_callback_available(&self, name: &str);
|
2021-01-28 10:55:26 +00:00
|
|
|
|
|
|
|
fn on_fs_command(&self, command: &str, args: &str) -> bool;
|
2020-08-31 21:32:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub trait ExternalInterfaceMethod {
|
2023-01-06 23:33:31 +00:00
|
|
|
fn call(&self, context: &mut UpdateContext<'_, '_>, args: &[Value]) -> Value;
|
2020-08-31 21:32:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<F> ExternalInterfaceMethod for F
|
|
|
|
where
|
2023-01-06 23:33:31 +00:00
|
|
|
F: Fn(&mut UpdateContext<'_, '_>, &[Value]) -> Value,
|
2020-08-31 21:32:12 +00:00
|
|
|
{
|
2023-01-06 23:33:31 +00:00
|
|
|
fn call(&self, context: &mut UpdateContext<'_, '_>, args: &[Value]) -> Value {
|
2020-08-31 21:32:12 +00:00
|
|
|
self(context, args)
|
|
|
|
}
|
2020-08-28 10:43:21 +00:00
|
|
|
}
|
2020-08-27 21:27:54 +00:00
|
|
|
|
2021-02-18 02:38:55 +00:00
|
|
|
#[derive(Default, Collect)]
|
|
|
|
#[collect(no_drop)]
|
2020-08-27 22:16:53 +00:00
|
|
|
pub struct ExternalInterface<'gc> {
|
2021-02-18 02:38:55 +00:00
|
|
|
#[collect(require_static)]
|
2020-08-27 21:27:54 +00:00
|
|
|
providers: Vec<Box<dyn ExternalInterfaceProvider>>,
|
2020-08-28 15:24:26 +00:00
|
|
|
callbacks: BTreeMap<String, Callback<'gc>>,
|
2020-08-27 22:16:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> ExternalInterface<'gc> {
|
2020-08-27 21:27:54 +00:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Self::default()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_provider(&mut self, provider: Box<dyn ExternalInterfaceProvider>) {
|
|
|
|
self.providers.push(provider);
|
|
|
|
}
|
|
|
|
|
2020-08-27 22:16:53 +00:00
|
|
|
pub fn add_callback(&mut self, name: String, callback: Callback<'gc>) {
|
2020-09-02 19:02:32 +00:00
|
|
|
self.callbacks.insert(name.clone(), callback);
|
|
|
|
for provider in &self.providers {
|
|
|
|
provider.on_callback_available(&name);
|
|
|
|
}
|
2020-08-27 22:16:53 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 10:24:19 +00:00
|
|
|
pub fn get_callback(&self, name: &str) -> Option<Callback<'gc>> {
|
|
|
|
self.callbacks.get(name).cloned()
|
|
|
|
}
|
|
|
|
|
2020-08-31 21:32:12 +00:00
|
|
|
pub fn get_method_for(&self, name: &str) -> Option<Box<dyn ExternalInterfaceMethod>> {
|
2020-08-28 10:43:21 +00:00
|
|
|
for provider in &self.providers {
|
2020-08-31 21:32:12 +00:00
|
|
|
if let Some(method) = provider.get_method(name) {
|
|
|
|
return Some(method);
|
2020-08-28 10:43:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-31 21:32:12 +00:00
|
|
|
None
|
2020-08-28 10:43:21 +00:00
|
|
|
}
|
|
|
|
|
2020-08-27 21:27:54 +00:00
|
|
|
pub fn available(&self) -> bool {
|
|
|
|
!self.providers.is_empty()
|
|
|
|
}
|
2021-01-28 10:55:26 +00:00
|
|
|
|
|
|
|
pub fn invoke_fs_command(&self, command: &str, args: &str) -> bool {
|
|
|
|
for provider in &self.providers {
|
|
|
|
if provider.on_fs_command(command, args) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
2020-08-27 21:27:54 +00:00
|
|
|
}
|