avm2: Re-use same AMFValue for the same Object ptr

This preserves object identity across a serialization
round-trip. Unfortunately, we don't currently implement this
correctly in flash_lso, so I've added a stub message.

Once flash_lso is fixed, this code will start working. For now,
it just allows us to detect (via the stub) if this is actually
used by an SWF.
This commit is contained in:
Aaron Hill 2023-11-09 21:09:18 -05:00
parent e8ccbf4e2c
commit b056e12f4b
7 changed files with 98 additions and 16 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -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 }

View File

@ -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<Object<'gc>, Rc<AmfValue>>;
/// 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<AmfValue> {
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<Element>,
static_properties: Option<&mut Vec<String>>,
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));
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<Element> {
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<AmfValue>' 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>,

View File

@ -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(

View File

@ -17,7 +17,14 @@ fn new_lso<'gc>(
data: Object<'gc>,
) -> Result<Lso, Error<'gc>> {
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('/')

View File

@ -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")?;

View File

@ -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")?;