diff --git a/Cargo.lock b/Cargo.lock index f6b9667bf..aee882d46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/core/Cargo.toml b/core/Cargo.toml index d5839981d..4315b098b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -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 } diff --git a/core/src/avm1/globals/shared_object.rs b/core/src/avm1/globals/shared_object.rs index d9481fae0..01fe9b92f 100644 --- a/core/src/avm1/globals/shared_object.rs +++ b/core/src/avm1/globals/shared_object.rs @@ -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 { - 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, + 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>, +) -> 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::() { 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, 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(|| "".to_string()), - AMFVersion::AMF0, ); let bytes = flash_lso::write::write_to_bytes(&mut lso).unwrap_or_default(); diff --git a/core/src/avm2/amf.rs b/core/src/avm2/amf.rs index 96a5e5c0c..2d6c2cf7b 100644 --- a/core/src/avm2/amf.rs +++ b/core/src/avm2/amf.rs @@ -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 }