Fix vtable property amf serialization

This commit is contained in:
Aaron Hill 2023-11-07 18:33:51 -05:00 committed by Nathan Adams
parent 10d6157755
commit bab2a80d64
6 changed files with 74 additions and 33 deletions

View File

@ -10,6 +10,7 @@ use enumset::EnumSet;
use flash_lso::types::{AMFVersion, Element, Lso};
use flash_lso::types::{Attribute, ClassDefinition, Value as AmfValue};
use super::property::Property;
use super::{Namespace, QName};
/// Serialize a Value to an AmfValue
@ -45,7 +46,8 @@ pub fn serialize_value<'gc>(
Some(AmfValue::Undefined)
} else if o.as_array_storage().is_some() {
let mut values = Vec::new();
recursive_serialize(activation, o, &mut values, &mut vec![], amf_version).unwrap();
// Don't serialize properties from the vtable (we don't want a 'length' field)
recursive_serialize(activation, o, &mut values, None, amf_version).unwrap();
let mut dense = vec![];
let mut sparse = vec![];
@ -155,7 +157,7 @@ pub fn serialize_value<'gc>(
activation,
o,
&mut object_body,
&mut static_properties,
Some(&mut static_properties),
amf_version,
)
.unwrap();
@ -182,23 +184,31 @@ pub fn recursive_serialize<'gc>(
activation: &mut Activation<'_, 'gc>,
obj: Object<'gc>,
elements: &mut Vec<Element>,
static_properties: &mut Vec<String>,
static_properties: Option<&mut Vec<String>>,
amf_version: AMFVersion,
) -> Result<(), Error<'gc>> {
if let Some(vtable) = obj.vtable() {
let mut props = vtable.public_properties();
// Flash appears to use vtable iteration order, but we sort ours
// to make our test output consistent.
props.sort_by_key(|(name, _)| name.to_utf8_lossy().to_string());
for (name, _) in props {
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));
static_properties.push(name);
if let Some(static_properties) = static_properties {
if let Some(vtable) = obj.vtable() {
let mut props = vtable.public_properties();
// Flash appears to use vtable iteration order, but we sort ours
// to make our test output consistent.
props.sort_by_key(|(name, _)| name.to_utf8_lossy().to_string());
for (name, prop) in props {
if let Property::Virtual { get, set } = prop {
if !(get.is_some() && set.is_some()) {
continue;
}
}
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));
static_properties.push(name);
}
}
}
}
let mut last_index = obj.get_next_enumerant(0, activation)?;
while let Some(index) = last_index {
let name = obj

View File

@ -17,13 +17,7 @@ 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,
&mut vec![],
AMFVersion::AMF3,
)?;
crate::avm2::amf::recursive_serialize(activation, data, &mut elements, None, AMFVersion::AMF3)?;
Ok(Lso::new(
elements,
name.split('/')

View File

@ -761,7 +761,7 @@ pub fn read_object<'gc>(
let mut decoder = AMF0Decoder::default();
let (extra, amf) = decoder
.parse_single_element(bytes)
.map_err(|e| format!("Error: Invalid AMF0 object: {e:?}"))?;
.map_err(|_| "Error: Invalid object")?;
(
extra.len(),
crate::avm2::amf::deserialize_value(activation, &amf)?,
@ -771,7 +771,7 @@ pub fn read_object<'gc>(
let mut decoder = AMF3Decoder::default();
let (extra, amf) = decoder
.parse_single_element(bytes)
.map_err(|e| format!("Error: Invalid AMF3 object: {e:?}"))?;
.map_err(|_| "Error: Invalid object")?;
(
extra.len(),
crate::avm2::amf::deserialize_value(activation, &amf)?,
@ -782,6 +782,7 @@ pub fn read_object<'gc>(
bytearray.set_position(bytearray.len() - bytes_left);
return Ok(value);
}
Ok(Value::Undefined)
}

View File

@ -9,17 +9,43 @@ import flash.utils.ByteArray;
import flash.net.registerClassAlias;
class MyClass {
public var secondProp: Object;
public var firstProp: String;
public var thirdProp: Number;
private var privProp: String = "Default Private prop";
public function MyClass(priv:String = "Constructor private prop") {
this.privProp = priv;
}
public function toString() {
trace("MyClass(firstProp= " + this.firstProp + " secondProp=" + this.secondProp + " thirdProp=" + this.thirdProp + " privProp=" + this.privProp);
trace("MyClass(firstProp= " + this.firstProp + " privProp=" + this.privProp);
}
}
class GetterSetterClass {
public function get getAndSet(): String {
trace("Called getAndSet getter");
return "getAndSet getter value";
}
public function set getAndSet(val: String):void {
trace("Called getAndSet setter: " + val);
}
public function get getterOnly(): String {
trace("Called getterOnly");
return "getterOnly value";
}
public function set setterOnly(val: String): void {
trace("Called setterOnly: " + val);
}
AS3 var myAS3Var: String = "AS3 string";
public function toString():String {
return "GetterSetterClass(myAS3Var=" + this.myAS3Var + ")";
}
}
@ -32,8 +58,6 @@ registerClassAlias("MyClassAlias", MyClass);
var mycls = new MyClass("Overwritten private prop");
mycls.firstProp = "Hello";
mycls.secondProp = null;
mycls.thirdProp = -5.1;
// Note - Flash player appears to serialize properties in
// vtable order, which cannot in general reproduce. Our raw
// bytes match for this particular class definition, but all
@ -41,6 +65,10 @@ mycls.thirdProp = -5.1;
// in order to make it easier to match the exact bytes from Flash Player
roundtrip(mycls);
var getterSetter = new GetterSetterClass();
getterSetter.myAS3Var = "Overwritten as3 str";
roundtrip(getterSetter);
function dump(obj: *) {
var keys = [];
for (var key in obj) {
@ -54,7 +82,7 @@ function dump(obj: *) {
trace(out);
}
function roundtrip(obj: Object) {
function roundtrip(obj: Object): Object {
trace("Original: [" + obj + "] class: " + getQualifiedClassName(obj));
dump(obj);
var out = new ByteArray();
@ -71,4 +99,5 @@ function roundtrip(obj: Object) {
trace("Deserialized: [" + readBack + "] class: " + getQualifiedClassName(readBack));
dump(readBack);
trace()
return readBack;
}

View File

@ -10,11 +10,18 @@ Serialized: 10,11,1,11,102,105,114,115,116,6,11,72,101,108,108,111,1
Deserialized: [[object Object]] class: Object
first=Hello,
MyClass(firstProp= Hello secondProp=null thirdProp=-5.1 privProp=Overwritten private prop
MyClass(firstProp= Hello privProp=Overwritten private prop
Original: [undefined] class: Test.as$38::MyClass
Serialized: 10,51,25,77,121,67,108,97,115,115,65,108,105,97,115,19,102,105,114,115,116,80,114,111,112,21,115,101,99,111,110,100,80,114,111,112,19,116,104,105,114,100,80,114,111,112,6,11,72,101,108,108,111,1,5,192,20,102,102,102,102,102,102
MyClass(firstProp= Hello secondProp=null thirdProp=-5.1 privProp=Constructor private prop
Serialized: 10,19,25,77,121,67,108,97,115,115,65,108,105,97,115,19,102,105,114,115,116,80,114,111,112,6,11,72,101,108,108,111
MyClass(firstProp= Hello privProp=Constructor private prop
Deserialized: [undefined] class: Test.as$38::MyClass
Original: [GetterSetterClass(myAS3Var=Overwritten as3 str)] class: Test.as$38::GetterSetterClass
Called getAndSet getter
Serialized: 10,19,1,19,103,101,116,65,110,100,83,101,116,6,45,103,101,116,65,110,100,83,101,116,32,103,101,116,116,101,114,32,118,97,108,117,101
Deserialized: [[object Object]] class: Object
getAndSet=getAndSet getter value,