diff --git a/Cargo.lock b/Cargo.lock index 8a13b4bb4..dcad9a34f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1570,7 +1570,7 @@ dependencies = [ [[package]] name = "flash-lso" version = "0.6.0" -source = "git+https://github.com/ruffle-rs/rust-flash-lso?rev=450234ad4225facff08226c31468fb7cf9fb8197#450234ad4225facff08226c31468fb7cf9fb8197" +source = "git+https://github.com/ruffle-rs/rust-flash-lso?rev=2f976fb15b30aa4c5cb398710dc5e31a21004e57#2f976fb15b30aa4c5cb398710dc5e31a21004e57" dependencies = [ "cookie-factory", "enumset", diff --git a/core/Cargo.toml b/core/Cargo.toml index d2d28e650..72633c2dd 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -40,7 +40,7 @@ serde = { version = "1.0.190", 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.7" -flash-lso = { git = "https://github.com/ruffle-rs/rust-flash-lso", rev = "450234ad4225facff08226c31468fb7cf9fb8197" } +flash-lso = { git = "https://github.com/ruffle-rs/rust-flash-lso", rev = "2f976fb15b30aa4c5cb398710dc5e31a21004e57" } lzma-rs = {version = "0.3.0", optional = true } dasp = { version = "0.11.0", features = ["interpolate", "interpolate-linear", "signal"], optional = true } symphonia = { version = "0.5.3", default-features = false, features = ["mp3"], optional = true } diff --git a/core/src/avm2/amf.rs b/core/src/avm2/amf.rs index 6ae3adf62..017412344 100644 --- a/core/src/avm2/amf.rs +++ b/core/src/avm2/amf.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use crate::avm2::api_version::ApiVersion; use crate::avm2::bytearray::ByteArrayStorage; use crate::avm2::object::{ByteArrayObject, TObject, VectorObject}; @@ -5,19 +7,24 @@ use crate::avm2::vector::VectorStorage; use crate::avm2::ArrayObject; use crate::avm2::ArrayStorage; use crate::avm2::{Activation, Error, Object, Value}; +use crate::avm2_stub_method; use crate::string::AvmString; use enumset::EnumSet; use flash_lso::types::{AMFVersion, Element, Lso}; use flash_lso::types::{Attribute, ClassDefinition, Value as AmfValue}; +use fnv::FnvHashMap; use super::property::Property; use super::{ClassObject, Namespace, QName}; +pub type ObjectTable<'gc> = FnvHashMap, Rc>; + /// Serialize a Value to an AmfValue pub fn serialize_value<'gc>( activation: &mut Activation<'_, 'gc>, elem: Value<'gc>, amf_version: AMFVersion, + object_table: &mut ObjectTable<'gc>, ) -> Option { match elem { Value::Undefined => Some(AmfValue::Undefined), @@ -47,7 +54,8 @@ pub fn serialize_value<'gc>( } else if o.as_array_storage().is_some() { let mut values = Vec::new(); // Don't serialize properties from the vtable (we don't want a 'length' field) - recursive_serialize(activation, o, &mut values, None, amf_version).unwrap(); + recursive_serialize(activation, o, &mut values, None, amf_version, object_table) + .unwrap(); let mut dense = vec![]; let mut sparse = vec![]; @@ -101,7 +109,7 @@ pub fn serialize_value<'gc>( let obj_vec: Vec<_> = vec .iter() .map(|v| { - serialize_value(activation, v, amf_version) + serialize_value(activation, v, amf_version, object_table) .expect("Unexpected non-object value in object vector") }) .collect(); @@ -139,6 +147,7 @@ pub fn serialize_value<'gc>( &mut object_body, Some(&mut static_properties), amf_version, + object_table, ) .unwrap(); Some(AmfValue::Object( @@ -216,6 +225,7 @@ pub fn recursive_serialize<'gc>( elements: &mut Vec, static_properties: Option<&mut Vec>, amf_version: AMFVersion, + object_table: &mut ObjectTable<'gc>, ) -> Result<(), Error<'gc>> { if let Some(static_properties) = static_properties { if let Some(vtable) = obj.vtable() { @@ -230,9 +240,15 @@ pub fn recursive_serialize<'gc>( } } let value = obj.get_public_property(name, activation)?; - if let Some(value) = serialize_value(activation, value, amf_version) { - let name = name.to_utf8_lossy().to_string(); - elements.push(Element::new(name.clone(), value)); + let name = name.to_utf8_lossy().to_string(); + if let Some(elem) = get_or_create_element( + activation, + name.clone(), + value, + object_table, + amf_version, + ) { + elements.push(elem); static_properties.push(name); } } @@ -246,14 +262,55 @@ pub fn recursive_serialize<'gc>( .coerce_to_string(activation)?; let value = obj.get_public_property(name, activation)?; - if let Some(value) = serialize_value(activation, value, amf_version) { - elements.push(Element::new(name.to_utf8_lossy(), value)); + let name = name.to_utf8_lossy().to_string(); + if let Some(elem) = + get_or_create_element(activation, name.clone(), value, object_table, amf_version) + { + elements.push(elem); } last_index = obj.get_next_enumerant(index, activation)?; } Ok(()) } +fn get_or_create_element<'gc>( + activation: &mut Activation<'_, 'gc>, + name: String, + val: Value<'gc>, + object_table: &mut ObjectTable<'gc>, + amf_version: AMFVersion, +) -> Option { + if let Some(obj) = val.as_object() { + let rc_val = match object_table.get(&obj) { + Some(rc_val) => { + // Even though we'll clone the same 'Rc' for each occurrence + // of 'Object', flash_lso doesn't serialize this correctly yet. + avm2_stub_method!( + activation, + "flash.utils.ByteArray", + "writeObject", + "with same Object used multiple times" + ); + Some(rc_val.clone()) + } + None => { + if let Some(value) = serialize_value(activation, val, amf_version, object_table) { + let rc_val = Rc::new(value); + // We cannot use Entry, since we need to pass in 'object_table' to 'serialize_value' + object_table.insert(obj, rc_val.clone()); + Some(rc_val) + } else { + None + } + } + }; + return rc_val.map(|val| Element::new(name, val)); + } else if let Some(value) = serialize_value(activation, val, amf_version, object_table) { + return Some(Element::new(name, Rc::new(value))); + } + None +} + /// Deserialize a AmfValue to a Value pub fn deserialize_value<'gc>( activation: &mut Activation<'_, 'gc>, diff --git a/core/src/avm2/globals/flash/net/net_connection.rs b/core/src/avm2/globals/flash/net/net_connection.rs index d09fb281c..66ffd3397 100644 --- a/core/src/avm2/globals/flash/net/net_connection.rs +++ b/core/src/avm2/globals/flash/net/net_connection.rs @@ -12,6 +12,7 @@ use crate::{ use flash_lso::packet::Header; use flash_lso::types::AMFVersion; use flash_lso::types::Value as AMFValue; +use fnv::FnvHashMap; use ruffle_wstr::WStr; use std::rc::Rc; @@ -255,8 +256,10 @@ pub fn call<'gc>( .and_then(|o| o.as_responder()); let mut arguments = Vec::new(); + let mut object_table = FnvHashMap::default(); for arg in &args[2..] { - if let Some(value) = serialize_value(activation, *arg, AMFVersion::AMF0) { + if let Some(value) = serialize_value(activation, *arg, AMFVersion::AMF0, &mut object_table) + { arguments.push(Rc::new(value)); } } @@ -305,7 +308,14 @@ pub fn add_header<'gc>( let name = args.get_string(activation, 0)?; let must_understand = args.get_bool(1); - let value = serialize_value(activation, args[2], AMFVersion::AMF0).unwrap_or(AMFValue::Null); + // FIXME - do we re-use the same object reference table for all headers? + let value = serialize_value( + activation, + args[2], + AMFVersion::AMF0, + &mut Default::default(), + ) + .unwrap_or(AMFValue::Null); if let Some(handle) = connection.handle() { activation.context.net_connections.set_header( diff --git a/core/src/avm2/globals/flash/net/shared_object.rs b/core/src/avm2/globals/flash/net/shared_object.rs index ad47970e1..88dad4843 100644 --- a/core/src/avm2/globals/flash/net/shared_object.rs +++ b/core/src/avm2/globals/flash/net/shared_object.rs @@ -17,7 +17,14 @@ fn new_lso<'gc>( data: Object<'gc>, ) -> Result> { let mut elements = Vec::new(); - crate::avm2::amf::recursive_serialize(activation, data, &mut elements, None, AMFVersion::AMF3)?; + crate::avm2::amf::recursive_serialize( + activation, + data, + &mut elements, + None, + AMFVersion::AMF3, + &mut Default::default(), + )?; Ok(Lso::new( elements, name.split('/') diff --git a/core/src/avm2/globals/flash/net/socket.rs b/core/src/avm2/globals/flash/net/socket.rs index 7e98d1f2f..fa92ba35e 100644 --- a/core/src/avm2/globals/flash/net/socket.rs +++ b/core/src/avm2/globals/flash/net/socket.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use crate::avm2::bytearray::{Endian, ObjectEncoding}; use crate::avm2::error::{io_error, make_error_2008, security_error}; pub use crate::avm2::object::socket_allocator; @@ -644,8 +646,10 @@ pub fn write_object<'gc>( ObjectEncoding::Amf0 => AMFVersion::AMF0, ObjectEncoding::Amf3 => AMFVersion::AMF3, }; - if let Some(amf) = crate::avm2::amf::serialize_value(activation, obj, amf_version) { - let element = Element::new("", amf); + if let Some(amf) = + crate::avm2::amf::serialize_value(activation, obj, amf_version, &mut Default::default()) + { + let element = Element::new("", Rc::new(amf)); let mut lso = flash_lso::types::Lso::new(vec![element], "", amf_version); let bytes = flash_lso::write::write_to_bytes(&mut lso) .map_err(|_| "Failed to serialize object")?; diff --git a/core/src/avm2/globals/flash/utils/byte_array.rs b/core/src/avm2/globals/flash/utils/byte_array.rs index 4a1cb14c1..799064b6d 100644 --- a/core/src/avm2/globals/flash/utils/byte_array.rs +++ b/core/src/avm2/globals/flash/utils/byte_array.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use crate::avm2::activation::Activation; use crate::avm2::bytearray::{Endian, ObjectEncoding}; use crate::avm2::error::make_error_2008; @@ -797,8 +799,10 @@ pub fn write_object<'gc>( ObjectEncoding::Amf0 => AMFVersion::AMF0, ObjectEncoding::Amf3 => AMFVersion::AMF3, }; - if let Some(amf) = crate::avm2::amf::serialize_value(activation, obj, amf_version) { - let element = Element::new("", amf); + if let Some(amf) = + crate::avm2::amf::serialize_value(activation, obj, amf_version, &mut Default::default()) + { + let element = Element::new("", Rc::new(amf)); let mut lso = flash_lso::types::Lso::new(vec![element], "", amf_version); let bytes = flash_lso::write::write_to_bytes(&mut lso) .map_err(|_| "Failed to serialize object")?;