avm1: Fix panic on writing self-referential SharedObjects
This commit is contained in:
parent
ae1853f639
commit
27bc6e9609
|
@ -1166,17 +1166,6 @@ version = "2.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
|
||||
[[package]]
|
||||
name = "derive-try-from-primitive"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302ccf094df1151173bb6f5a2282fcd2f45accd5eae1bdf82dcbfefbc501ad5c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
|
@ -1384,7 +1373,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e875f1719c16de097dee81ed675e2d9bb63096823ed3f0ca827b7dea3028bbbb"
|
||||
dependencies = [
|
||||
"enumset_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1517,11 +1505,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "flash-lso"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/ruffle-rs/rust-flash-lso?rev=8376453eddddbe701031a091c0eed94068fa5649#8376453eddddbe701031a091c0eed94068fa5649"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/ruffle-rs/rust-flash-lso?rev=3669a352c14192d0d301e594ae6047ae99725006#3669a352c14192d0d301e594ae6047ae99725006"
|
||||
dependencies = [
|
||||
"cookie-factory",
|
||||
"derive-try-from-primitive",
|
||||
"enumset",
|
||||
"nom",
|
||||
"thiserror",
|
||||
|
|
|
@ -40,7 +40,7 @@ serde = { version = "1.0.171", features = ["derive"] }
|
|||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
nellymoser-rs = { git = "https://github.com/ruffle-rs/nellymoser", rev = "4a33521c29a918950df8ae9fe07e527ac65553f5", optional = true }
|
||||
regress = "0.6"
|
||||
flash-lso = { git = "https://github.com/ruffle-rs/rust-flash-lso", rev = "8376453eddddbe701031a091c0eed94068fa5649" }
|
||||
flash-lso = { git = "https://github.com/ruffle-rs/rust-flash-lso", rev = "3669a352c14192d0d301e594ae6047ae99725006" }
|
||||
lzma-rs = {version = "0.3.0", optional = true }
|
||||
dasp = { git = "https://github.com/RustAudio/dasp", rev = "f05a703", features = ["interpolate", "interpolate-linear", "signal"], optional = true }
|
||||
symphonia = { version = "0.5.3", default-features = false, features = ["mp3"], optional = true }
|
||||
|
|
|
@ -10,10 +10,11 @@ use crate::avm1_stub;
|
|||
use crate::context::GcContext;
|
||||
use crate::display_object::TDisplayObject;
|
||||
use crate::string::AvmString;
|
||||
use flash_lso::types::Value as AmfValue;
|
||||
use flash_lso::types::{AMFVersion, Element, Lso};
|
||||
|
||||
use flash_lso::amf0::read::AMF0Decoder;
|
||||
use flash_lso::amf0::writer::{Amf0Writer, CacheKey, ObjWriter};
|
||||
use flash_lso::types::{Lso, Reference, Value as AmfValue};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
const PROTO_DECLS: &[Declaration] = declare_properties! {
|
||||
"clear" => method(clear; DONT_ENUM | DONT_DELETE);
|
||||
|
@ -55,64 +56,73 @@ pub fn get_disk_usage<'gc>(
|
|||
Ok(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Serialize a Value to an AmfValue
|
||||
fn serialize_value<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
elem: Value<'gc>,
|
||||
) -> Option<AmfValue> {
|
||||
match elem {
|
||||
Value::Undefined | Value::MovieClip(_) => Some(AmfValue::Undefined),
|
||||
Value::Null => Some(AmfValue::Null),
|
||||
Value::Bool(b) => Some(AmfValue::Bool(b)),
|
||||
Value::Number(f) => Some(AmfValue::Number(f)),
|
||||
Value::String(s) => Some(AmfValue::String(s.to_string())),
|
||||
Value::Object(o) => {
|
||||
// TODO: Find a more general rule for which object types should be skipped,
|
||||
// and which turn into undefined.
|
||||
if o.as_executable().is_some() {
|
||||
None
|
||||
} else if o.as_display_object().is_some() {
|
||||
Some(AmfValue::Undefined)
|
||||
} else if o.as_array_object().is_some() {
|
||||
let mut values = Vec::new();
|
||||
recursive_serialize(activation, o, &mut values);
|
||||
|
||||
// TODO: What happens if an exception is thrown here?
|
||||
let length = o.length(activation).unwrap();
|
||||
Some(AmfValue::ECMAArray(vec![], values, length as u32))
|
||||
} else if let Some(xml_node) = o.as_xml_node() {
|
||||
// TODO: What happens if an exception is thrown here?
|
||||
let string = xml_node.into_string(activation).unwrap();
|
||||
Some(AmfValue::XML(string.to_utf8_lossy().into_owned(), true))
|
||||
} else if let NativeObject::Date(date) = o.native() {
|
||||
Some(AmfValue::Date(date.read().time(), None))
|
||||
} else {
|
||||
let mut object_body = Vec::new();
|
||||
recursive_serialize(activation, o, &mut object_body);
|
||||
Some(AmfValue::Object(object_body, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize an Object and any children to a JSON object
|
||||
fn recursive_serialize<'gc>(
|
||||
fn recursive_serialize<'gc, 'b, 'c>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
obj: Object<'gc>,
|
||||
elements: &mut Vec<Element>,
|
||||
writer: &'b mut dyn ObjWriter<'c>,
|
||||
) {
|
||||
// Reversed to match flash player ordering
|
||||
for element_name in obj.get_keys(activation, false).into_iter().rev() {
|
||||
if let Ok(elem) = obj.get(element_name, activation) {
|
||||
if let Some(v) = serialize_value(activation, elem) {
|
||||
elements.push(Element::new(element_name.to_utf8_lossy(), v));
|
||||
let name = element_name.to_utf8_lossy();
|
||||
|
||||
match elem {
|
||||
Value::Object(o) => {
|
||||
if o.as_executable().is_some() {
|
||||
} else if o.as_display_object().is_some() {
|
||||
writer.undefined(name.as_ref())
|
||||
} else if o.as_array_object().is_some() {
|
||||
let (aw, token) = writer.array(CacheKey::from_ptr(o.as_ptr()));
|
||||
|
||||
if let Some(mut aw) = aw {
|
||||
recursive_serialize(activation, o, &mut aw);
|
||||
|
||||
// TODO: What happens if an exception is thrown here?
|
||||
let length = o
|
||||
.length(activation)
|
||||
.expect("Failed to get length for SharedObject array");
|
||||
|
||||
aw.commit(name, length as u32);
|
||||
} else {
|
||||
writer.reference(name.as_ref(), token);
|
||||
}
|
||||
} else if let Some(xml_node) = o.as_xml_node() {
|
||||
// TODO: What happens if an exception is thrown here?
|
||||
let string = xml_node
|
||||
.into_string(activation)
|
||||
.expect("Failed to convert xml to string in SharedObject");
|
||||
writer.xml(name.as_ref(), string.to_utf8_lossy().as_ref(), true)
|
||||
} else if let NativeObject::Date(date) = o.native() {
|
||||
writer.date(name.as_ref(), date.read().time(), None)
|
||||
} else {
|
||||
let (ow, token) = writer.object(CacheKey::from_ptr(o.as_ptr()));
|
||||
|
||||
if let Some(mut ow) = ow {
|
||||
recursive_serialize(activation, o, &mut ow);
|
||||
ow.commit(name);
|
||||
} else {
|
||||
writer.reference(name.as_ref(), token);
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Number(f) => writer.number(name.as_ref(), f),
|
||||
Value::String(s) => writer.string(name.as_ref(), s.to_utf8_lossy().as_ref()),
|
||||
Value::Undefined | Value::MovieClip(_) => writer.undefined(name.as_ref()),
|
||||
Value::Null => writer.null(name.as_ref()),
|
||||
Value::Bool(b) => writer.bool(name.as_ref(), b),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize a AmfValue to a Value
|
||||
fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc>, val: &AmfValue) -> Value<'gc> {
|
||||
fn deserialize_value<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
val: &AmfValue,
|
||||
lso: &AMF0Decoder,
|
||||
reference_cache: &mut BTreeMap<Reference, Value<'gc>>,
|
||||
) -> Value<'gc> {
|
||||
match val {
|
||||
AmfValue::Null => Value::Null,
|
||||
AmfValue::Undefined => Value::Undefined,
|
||||
|
@ -125,7 +135,7 @@ fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc>, val: &AmfValue)
|
|||
array_constructor.construct(activation, &[(*len).into()])
|
||||
{
|
||||
for entry in associative {
|
||||
let value = deserialize_value(activation, entry.value());
|
||||
let value = deserialize_value(activation, entry.value(), lso, reference_cache);
|
||||
|
||||
if let Ok(i) = entry.name().parse::<i32>() {
|
||||
obj.set_element(activation, i, value).unwrap();
|
||||
|
@ -139,7 +149,14 @@ fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc>, val: &AmfValue)
|
|||
}
|
||||
}
|
||||
|
||||
obj.into()
|
||||
let v: Value<'gc> = obj.into();
|
||||
|
||||
// This should always be valid, but lets be sure
|
||||
if let Some(reference) = lso.as_reference(val) {
|
||||
reference_cache.insert(reference, v);
|
||||
}
|
||||
|
||||
v
|
||||
} else {
|
||||
Value::Undefined
|
||||
}
|
||||
|
@ -151,7 +168,7 @@ fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc>, val: &AmfValue)
|
|||
Some(activation.context.avm1.prototypes().object),
|
||||
);
|
||||
for entry in elements {
|
||||
let value = deserialize_value(activation, entry.value());
|
||||
let value = deserialize_value(activation, entry.value(), lso, reference_cache);
|
||||
let name = AvmString::new_utf8(activation.context.gc_context, &entry.name);
|
||||
obj.define_value(
|
||||
activation.context.gc_context,
|
||||
|
@ -160,7 +177,15 @@ fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc>, val: &AmfValue)
|
|||
Attribute::empty(),
|
||||
);
|
||||
}
|
||||
obj.into()
|
||||
|
||||
let v: Value<'gc> = obj.into();
|
||||
|
||||
// This should always be valid, but lets be sure
|
||||
if let Some(reference) = lso.as_reference(val) {
|
||||
reference_cache.insert(reference, v);
|
||||
}
|
||||
|
||||
v
|
||||
}
|
||||
AmfValue::Date(time, _) => {
|
||||
let date_proto = activation.context.avm1.prototypes().date_constructor;
|
||||
|
@ -186,7 +211,12 @@ fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc>, val: &AmfValue)
|
|||
Value::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
AmfValue::Reference(x) => {
|
||||
// This should always be a valid reference, but a "bad" file could create an invalid one
|
||||
// In that case we will just assume undefined
|
||||
let val = reference_cache.get(x).unwrap_or(&Value::Undefined);
|
||||
*val
|
||||
}
|
||||
_ => Value::Undefined,
|
||||
}
|
||||
}
|
||||
|
@ -195,17 +225,20 @@ fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc>, val: &AmfValue)
|
|||
fn deserialize_lso<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
lso: &Lso,
|
||||
decoder: &AMF0Decoder,
|
||||
) -> Result<Object<'gc>, Error<'gc>> {
|
||||
let obj = ScriptObject::new(
|
||||
activation.context.gc_context,
|
||||
Some(activation.context.avm1.prototypes().object),
|
||||
);
|
||||
|
||||
let mut reference_cache = BTreeMap::default();
|
||||
|
||||
for child in &lso.body {
|
||||
obj.define_value(
|
||||
activation.context.gc_context,
|
||||
AvmString::new_utf8(activation.context.gc_context, &child.name),
|
||||
deserialize_value(activation, child.value()),
|
||||
deserialize_value(activation, child.value(), decoder, &mut reference_cache),
|
||||
Attribute::empty(),
|
||||
);
|
||||
}
|
||||
|
@ -350,8 +383,9 @@ pub fn get_local<'gc>(
|
|||
|
||||
// Load the data object from storage if it existed prior
|
||||
if let Some(saved) = activation.context.storage.get(&full_name) {
|
||||
if let Ok(lso) = flash_lso::read::Reader::default().parse(&saved) {
|
||||
data = deserialize_lso(activation, &lso)?.into();
|
||||
let mut reader = flash_lso::read::Reader::default();
|
||||
if let Ok(lso) = reader.parse(&saved) {
|
||||
data = deserialize_lso(activation, &lso, &reader.amf0_decoder)?.into();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -479,15 +513,14 @@ pub fn flush<'gc>(
|
|||
let this_obj = this.as_shared_object().unwrap();
|
||||
let name = this_obj.get_name();
|
||||
|
||||
let mut elements = Vec::new();
|
||||
recursive_serialize(activation, data, &mut elements);
|
||||
let mut lso = Lso::new(
|
||||
elements,
|
||||
name.split('/')
|
||||
let mut w = Amf0Writer::default();
|
||||
recursive_serialize(activation, data, &mut w);
|
||||
let mut lso = w.commit_lso(
|
||||
&name
|
||||
.split('/')
|
||||
.last()
|
||||
.map(|e| e.to_string())
|
||||
.unwrap_or_else(|| "<unknown>".to_string()),
|
||||
AMFVersion::AMF0,
|
||||
);
|
||||
|
||||
let bytes = flash_lso::write::write_to_bytes(&mut lso).unwrap_or_default();
|
||||
|
|
|
@ -213,7 +213,8 @@ pub fn deserialize_value<'gc>(
|
|||
| AmfValue::VectorInt(..)
|
||||
| AmfValue::VectorObject(..)
|
||||
| AmfValue::Dictionary(..)
|
||||
| AmfValue::Custom(..) => {
|
||||
| AmfValue::Custom(..)
|
||||
| AmfValue::Reference(_) => {
|
||||
tracing::error!("Deserialization not yet implemented: {:?}", val);
|
||||
Value::Undefined
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue