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:
parent
38b4357f59
commit
3c64e8e249
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
@ -0,0 +1 @@
|
|||
num_ticks = 1
|
Loading…
Reference in New Issue