From 3c64e8e249d94282ae1245fe8aa3673c9aafee8c Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sat, 15 Jul 2023 23:21:46 -0400 Subject: [PATCH] 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., due to our broken vector handling. --- core/src/avm2/amf.rs | 94 ++++++++++++++++++-- tests/tests/swfs/avm2/amf_vector/Test.as | 52 +++++++++++ tests/tests/swfs/avm2/amf_vector/output.txt | 18 ++++ tests/tests/swfs/avm2/amf_vector/test.fla | Bin 0 -> 3815 bytes tests/tests/swfs/avm2/amf_vector/test.swf | Bin 0 -> 1154 bytes tests/tests/swfs/avm2/amf_vector/test.toml | 1 + 6 files changed, 157 insertions(+), 8 deletions(-) create mode 100755 tests/tests/swfs/avm2/amf_vector/Test.as create mode 100644 tests/tests/swfs/avm2/amf_vector/output.txt create mode 100755 tests/tests/swfs/avm2/amf_vector/test.fla create mode 100755 tests/tests/swfs/avm2/amf_vector/test.swf create mode 100644 tests/tests/swfs/avm2/amf_vector/test.toml diff --git a/core/src/avm2/amf.rs b/core/src/avm2/amf.rs index 2d6c2cf7b..9c1e591df 100644 --- a/core/src/avm2/amf.rs +++ b/core/src/avm2/amf.rs @@ -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. 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::, _>>()?, + *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 } diff --git a/tests/tests/swfs/avm2/amf_vector/Test.as b/tests/tests/swfs/avm2/amf_vector/Test.as new file mode 100755 index 000000000..6e0b9ce94 --- /dev/null +++ b/tests/tests/swfs/avm2/amf_vector/Test.as @@ -0,0 +1,52 @@ +package { + import flash.utils.ByteArray; + import flash.utils.getQualifiedClassName; + + public class Test { + public function Test() { + roundtrip(Vector.([100, 200, 300])); + roundtrip(fixed(Vector.([500, 600]))); + roundtrip(Vector.([])); + + roundtrip(Vector.([-1, -200, 4])) + roundtrip(fixed(Vector.([-100]))); + + roundtrip(Vector.([-0.0, 0.0, -1, Infinity, 5, NaN])); + + // FIXME - enable these once Ruffle correctly handles Vector. + + /*roundtrip(Vector.([new Object(), 30, null, undefined, true, "Hello"])); + roundtrip(Vector.<*>([new Object(), 30, null, undefined, true, "Hello"])); + + + var first = Vector.(["One", "Two"]); + var second = Vector.(["Three", "Four"]); + var vec = Vector.>([first, second]); + + roundtrip(vec); + + roundtrip(Vector.(["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)); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/amf_vector/output.txt b/tests/tests/swfs/avm2/amf_vector/output.txt new file mode 100644 index 000000000..e7d670683 --- /dev/null +++ b/tests/tests/swfs/avm2/amf_vector/output.txt @@ -0,0 +1,18 @@ +Original: [100,200,300] fixed: false class: __AS3__.vec::Vector. +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. +Original: [500,600] fixed: true class: __AS3__.vec::Vector. +Serialized: 14,5,1,0,0,1,244,0,0,2,88 +Deserialized: [500,600] fixed: true class: __AS3__.vec::Vector. +Original: [] fixed: false class: __AS3__.vec::Vector. +Serialized: 14,1,0 +Deserialized: [] fixed: false class: __AS3__.vec::Vector. +Original: [-1,-200,4] fixed: false class: __AS3__.vec::Vector. +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. +Original: [-100] fixed: true class: __AS3__.vec::Vector. +Serialized: 13,3,1,255,255,255,156 +Deserialized: [-100] fixed: true class: __AS3__.vec::Vector. +Original: [0,0,-1,Infinity,5,NaN] fixed: false class: __AS3__.vec::Vector. +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. diff --git a/tests/tests/swfs/avm2/amf_vector/test.fla b/tests/tests/swfs/avm2/amf_vector/test.fla new file mode 100755 index 0000000000000000000000000000000000000000..f836aab715e5f131bb03d64d04623f82bee8fa3e GIT binary patch literal 3815 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 Qb^)IRAmI4|&p@F60ceHx)c^nh literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/amf_vector/test.swf b/tests/tests/swfs/avm2/amf_vector/test.swf new file mode 100755 index 0000000000000000000000000000000000000000..cbd57d61e3b30b257034eaaf21ce45fcf210ad45 GIT binary patch literal 1154 zcmV-|1bzEMS5qs)1^@tfoQ+gXbJ|82-dECUwSWzd1O7-HlSVinxFFljN6Dde>^gNi zG1E9@GKm=wu$XN@Fp?l~PnrCK-h1%$+#k?GXL9WkJ2Pp2K<`#&dM;fd@HjK+v^(0B z_IaLt-hKC#^a9}$LeWEnW&v$ZX$YYWX)qWp#=3L*ie9~^*Vebcy4`Kn?85DX!p%pV z+18_{8T2|H7Fs~NRc5i|3PP{X-&cJ=v>3{WQAR;~Il7}9qK}#5X8l3USH5Lc_uLTZ z*t}=DELZ0*SPc)y#CwF@?pih1=AD!);8MM5<{p~dddyp_$t{*WMeDZNVk_zYP@P2; zKM*|)pp8Y5v!V$BIE_q=JDsL^Kp(byoNY9DC;mZ8HQ2@4)7+ljt=F4u&zCu)(Rp5X z_RL;ub zvXw4xy4(tvN&+gCGQvyga?6!PS9iLt3bVCstIjR%9>_c9jw}SwliOVvR}RExfNM50 zYxm6Rci6SfDhuarzRxYQnM*#S#pJLMxulk?itL?SG8~*j*X2zozR%pRyJnNuIje2> z{Eo<(-V?s-wrw7SpWEDJL6Nmi+u<&6TQNV}H=-bF6{jyf3yHL|>sox;=vs94RA+La^^~X%=FFEyxQjpeROCC~m%oI?d4f{aI zHyNaU`Fk*k>dM$A(m&;XEWoM7q>qLLnuV;S56Tf8mnRBpux8=DpQ5DTxY+4p-#CE_ zXwgd^Et!+v<)bgfPfk%4{Hvdb?z0z>egX@ybOM*88z-ESVk!1k1eL8|KXwI=gIDl4 z`l`rKkwjr;`i95?7iY_0l;H_lXh>o%3mS{kg@UrF0HJVHC=Cya(gO)y_ap%mS-^?D zA<9^sd$;O=j{0?ypOc2HChTdtZ!qLr&l}|nM){&qUNFj+jIt`OC$JE-27dRcH!GHW UHa$Oj*FN|Gqt6BN4+wAL*430fa{vGU literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/amf_vector/test.toml b/tests/tests/swfs/avm2/amf_vector/test.toml new file mode 100644 index 000000000..67f15e863 --- /dev/null +++ b/tests/tests/swfs/avm2/amf_vector/test.toml @@ -0,0 +1 @@ +num_ticks = 1 \ No newline at end of file