avm2: Implement AMF Vector serialization/deserialization

Some of the tests are currently disabled because (separate from this PR)
we can't actually run `toString()` on a Vector.<Object>, due to our
broken vector handling.
This commit is contained in:
Aaron Hill 2023-07-15 23:21:46 -04:00
parent 38b4357f59
commit 3c64e8e249
6 changed files with 157 additions and 8 deletions

View File

@ -1,5 +1,6 @@
use crate::avm2::bytearray::ByteArrayStorage;
use crate::avm2::object::{ByteArrayObject, TObject};
use crate::avm2::object::{ByteArrayObject, TObject, VectorObject};
use crate::avm2::vector::VectorStorage;
use crate::avm2::ArrayObject;
use crate::avm2::ArrayStorage;
use crate::avm2::{Activation, Error, Object, Value};
@ -61,6 +62,50 @@ pub fn serialize_value<'gc>(
let len = sparse.len() as u32;
Some(AmfValue::ECMAArray(dense, sparse, len))
}
} else if let Some(vec) = o.as_vector_storage() {
let val_type = vec.value_type();
if val_type == activation.avm2().classes().int {
let int_vec: Vec<_> = vec
.iter()
.map(|v| {
v.as_integer(activation.context.gc_context)
.expect("Unexpected non-int value in int vector")
})
.collect();
Some(AmfValue::VectorInt(int_vec, vec.is_fixed()))
} else if val_type == activation.avm2().classes().uint {
let uint_vec: Vec<_> = vec
.iter()
.map(|v| {
v.as_u32(activation.context.gc_context)
.expect("Unexpected non-uint value in int vector")
})
.collect();
Some(AmfValue::VectorUInt(uint_vec, vec.is_fixed()))
} else if val_type == activation.avm2().classes().number {
let num_vec: Vec<_> = vec
.iter()
.map(|v| {
v.as_number(activation.context.gc_context)
.expect("Unexpected non-uint value in int vector")
})
.collect();
Some(AmfValue::VectorDouble(num_vec, vec.is_fixed()))
} else {
let obj_vec: Vec<_> = vec
.iter()
.map(|v| {
serialize_value(activation, v, amf_version)
.expect("Unexpected non-object value in object vector")
})
.collect();
// Flash always uses an empty type name
Some(AmfValue::VectorObject(
obj_vec,
"".to_string(),
vec.is_fixed(),
))
}
} else if let Some(date) = o.as_date_object() {
date.date_time()
.map(|date_time| AmfValue::Date(date_time.timestamp_millis() as f64, None))
@ -208,13 +253,46 @@ pub fn deserialize_value<'gc>(
))],
)?
.into(),
AmfValue::VectorDouble(..)
| AmfValue::VectorUInt(..)
| AmfValue::VectorInt(..)
| AmfValue::VectorObject(..)
| AmfValue::Dictionary(..)
| AmfValue::Custom(..)
| AmfValue::Reference(_) => {
AmfValue::VectorDouble(vec, is_fixed) => {
let storage = VectorStorage::from_values(
vec.iter().map(|v| (*v).into()).collect(),
*is_fixed,
activation.avm2().classes().number,
);
VectorObject::from_vector(storage, activation)?.into()
}
AmfValue::VectorUInt(vec, is_fixed) => {
let storage = VectorStorage::from_values(
vec.iter().map(|v| (*v).into()).collect(),
*is_fixed,
activation.avm2().classes().uint,
);
VectorObject::from_vector(storage, activation)?.into()
}
AmfValue::VectorInt(vec, is_fixed) => {
let storage = VectorStorage::from_values(
vec.iter().map(|v| (*v).into()).collect(),
*is_fixed,
activation.avm2().classes().int,
);
VectorObject::from_vector(storage, activation)?.into()
}
AmfValue::VectorObject(vec, ty_name, is_fixed) => {
// Flash always serializes Vector.<SomeType> with an empty type name
if !ty_name.is_empty() {
tracing::error!("Tried to deserialize Vector with type name: {}", ty_name);
}
let value_type = activation.avm2().classes().object;
let storage = VectorStorage::from_values(
vec.iter()
.map(|v| deserialize_value(activation, v))
.collect::<Result<Vec<_>, _>>()?,
*is_fixed,
value_type,
);
VectorObject::from_vector(storage, activation)?.into()
}
AmfValue::Dictionary(..) | AmfValue::Custom(..) | AmfValue::Reference(_) => {
tracing::error!("Deserialization not yet implemented: {:?}", val);
Value::Undefined
}

View File

@ -0,0 +1,52 @@
package {
import flash.utils.ByteArray;
import flash.utils.getQualifiedClassName;
public class Test {
public function Test() {
roundtrip(Vector.<uint>([100, 200, 300]));
roundtrip(fixed(Vector.<uint>([500, 600])));
roundtrip(Vector.<uint>([]));
roundtrip(Vector.<int>([-1, -200, 4]))
roundtrip(fixed(Vector.<int>([-100])));
roundtrip(Vector.<Number>([-0.0, 0.0, -1, Infinity, 5, NaN]));
// FIXME - enable these once Ruffle correctly handles Vector.<Object>
/*roundtrip(Vector.<Object>([new Object(), 30, null, undefined, true, "Hello"]));
roundtrip(Vector.<*>([new Object(), 30, null, undefined, true, "Hello"]));
var first = Vector.<String>(["One", "Two"]);
var second = Vector.<String>(["Three", "Four"]);
var vec = Vector.<Vector.<String>>([first, second]);
roundtrip(vec);
roundtrip(Vector.<String>(["First string", "Second string"])); */
}
private function fixed(vec: Object): Object {
vec.fixed = true;
return vec;
}
private function roundtrip(v: Object) {
trace("Original: [" + v + "] fixed: " + v.fixed + " class: " + getQualifiedClassName(v));
var out = new ByteArray();
out.writeObject(v);
out.position = 0;
var bytes = []
for (var i = 0; i < out.length; i++) {
bytes.push(out.readUnsignedByte());
}
trace("Serialized: " + bytes);
out.position = 0;
var readBack = out.readObject();
trace("Deserialized: [" + readBack + "] fixed: " + readBack.fixed + " class: " + getQualifiedClassName(readBack));
}
}
}

View File

@ -0,0 +1,18 @@
Original: [100,200,300] fixed: false class: __AS3__.vec::Vector.<uint>
Serialized: 14,7,0,0,0,0,100,0,0,0,200,0,0,1,44
Deserialized: [100,200,300] fixed: false class: __AS3__.vec::Vector.<uint>
Original: [500,600] fixed: true class: __AS3__.vec::Vector.<uint>
Serialized: 14,5,1,0,0,1,244,0,0,2,88
Deserialized: [500,600] fixed: true class: __AS3__.vec::Vector.<uint>
Original: [] fixed: false class: __AS3__.vec::Vector.<uint>
Serialized: 14,1,0
Deserialized: [] fixed: false class: __AS3__.vec::Vector.<uint>
Original: [-1,-200,4] fixed: false class: __AS3__.vec::Vector.<int>
Serialized: 13,7,0,255,255,255,255,255,255,255,56,0,0,0,4
Deserialized: [-1,-200,4] fixed: false class: __AS3__.vec::Vector.<int>
Original: [-100] fixed: true class: __AS3__.vec::Vector.<int>
Serialized: 13,3,1,255,255,255,156
Deserialized: [-100] fixed: true class: __AS3__.vec::Vector.<int>
Original: [0,0,-1,Infinity,5,NaN] fixed: false class: __AS3__.vec::Vector.<Number>
Serialized: 15,13,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,191,240,0,0,0,0,0,0,127,240,0,0,0,0,0,0,64,20,0,0,0,0,0,0,255,248,0,0,0,0,0,0
Deserialized: [0,0,-1,Infinity,5,NaN] fixed: false class: __AS3__.vec::Vector.<Number>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_ticks = 1