Skip to content

Commit 4b5d980

Browse files
committed
avm2: Promote StrictArray only at NetConnection/LocalConnection call sites
Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
1 parent a2fdbaf commit 4b5d980

4 files changed

Lines changed: 115 additions & 23 deletions

File tree

core/src/avm1/globals/shared_object.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,31 @@ pub fn deserialize_value<'gc>(
198198
Value::Undefined
199199
}
200200
}
201+
AmfValue::StrictArray(_, values) => {
202+
// Real Flash uses `StrictArray` (AMF0 marker `0x0A`) for dense AS3
203+
// `Array`s sent over `NetConnection.call` / `LocalConnection.send`
204+
// (#16381), so the AVM1 receiver has to reconstruct an `Array`
205+
// here rather than falling through to `Undefined`.
206+
let array_constructor = activation.prototypes().array_constructor;
207+
if let Ok(Value::Object(obj)) =
208+
array_constructor.construct(activation, &[(values.len() as i32).into()])
209+
{
210+
let v: Value<'gc> = obj.into();
211+
212+
if let Some(reference) = lso.as_reference(val) {
213+
reference_cache.insert(reference, v);
214+
}
215+
216+
for (i, item) in values.iter().enumerate() {
217+
let value = deserialize_value(activation, item, lso, reference_cache);
218+
obj.set_element(activation, i as i32, value).unwrap();
219+
}
220+
221+
v
222+
} else {
223+
Value::Undefined
224+
}
225+
}
201226
AmfValue::Object(_, elements, _) => {
202227
// Deserialize Object
203228
let obj = Object::new(

core/src/avm2/amf.rs

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,12 @@ pub fn serialize_value<'gc>(
6464
}
6565
}
6666

67-
if amf_version == AMFVersion::AMF0 && sparse.is_empty() {
68-
// Dense-only AS3 `Array` in AMF0 must be serialized as
69-
// `StrictArray` (marker `0x0A`) so that Flash decoders see
70-
// an indexed array `["a", "b"]` rather than an associative
71-
// object `{0: "a", 1: "b"}`. Before this fix the AMF0 path
72-
// unconditionally emitted `ECMAArray` (marker `0x08`),
73-
// which broke real-world Flash Remoting endpoints that
74-
// round-trip AS3 arrays as call arguments (#16381).
75-
Some(AmfValue::StrictArray(ObjectId::INVALID, dense))
76-
} else {
77-
// Mixed dense + sparse arrays use ECMAArray for both AMF0
78-
// and AMF3. AMF3 has no `StrictArray` marker and uses
79-
// ECMAArray with both dense and sparse parts for every
80-
// AS3 `Array`.
81-
Some(AmfValue::ECMAArray(ObjectId::INVALID, dense, sparse, len))
82-
}
67+
// AS3 `Array` is serialized as `ECMAArray` for both AMF0 and AMF3.
68+
// Callers that need `StrictArray` semantics for dense AS3 `Array`s
69+
// (e.g. `NetConnection.call` / `LocalConnection.send` argument
70+
// encoding, see #16381) post-process the returned tree with
71+
// `promote_dense_ecma_to_strict_array`.
72+
Some(AmfValue::ECMAArray(ObjectId::INVALID, dense, sparse, len))
8373
} else if let Some(vec) = o.as_vector_storage() {
8474
let val_type = vec.value_type();
8575
if val_type == Some(activation.avm2().class_defs().int) {
@@ -193,6 +183,79 @@ pub fn serialize_value<'gc>(
193183
}
194184
}
195185

186+
/// Recursively rewrite dense `ECMAArray` nodes in `value` as `StrictArray`.
187+
///
188+
/// Real Flash encodes the argument tree for `NetConnection.call` and
189+
/// `LocalConnection.send` with `StrictArray` (marker `0x0A`) for every dense
190+
/// AS3 `Array`, while `ByteArray.writeObject` and `Socket.writeObject` keep
191+
/// the `ECMAArray` (marker `0x08`) encoding for those same arrays. The default
192+
/// `serialize_value` output uses `ECMAArray` to match the latter; the Flash
193+
/// Remoting code paths call this helper to convert the tree into the form
194+
/// real Flash sends on the wire (#16381).
195+
///
196+
/// An `ECMAArray` is treated as dense and promoted when its sparse portion is
197+
/// empty and its declared length matches the dense portion exactly. All other
198+
/// nodes are walked so that nested dense arrays inside objects, vectors,
199+
/// dictionaries and AMF3 wrappers are promoted too.
200+
pub fn promote_dense_ecma_to_strict_array(value: AmfValue) -> AmfValue {
201+
match value {
202+
AmfValue::ECMAArray(_, dense, sparse, len)
203+
if sparse.is_empty() && dense.len() as u32 == len =>
204+
{
205+
let promoted = dense.into_iter().map(promote_rc).collect();
206+
AmfValue::StrictArray(ObjectId::INVALID, promoted)
207+
}
208+
AmfValue::ECMAArray(id, dense, sparse, len) => {
209+
let promoted_dense = dense.into_iter().map(promote_rc).collect();
210+
let promoted_sparse = sparse
211+
.into_iter()
212+
.map(|e| Element::new(e.name, promote_rc(e.value)))
213+
.collect();
214+
AmfValue::ECMAArray(id, promoted_dense, promoted_sparse, len)
215+
}
216+
AmfValue::StrictArray(id, values) => {
217+
let promoted = values.into_iter().map(promote_rc).collect();
218+
AmfValue::StrictArray(id, promoted)
219+
}
220+
AmfValue::Object(id, elements, class) => {
221+
let promoted = elements
222+
.into_iter()
223+
.map(|e| Element::new(e.name, promote_rc(e.value)))
224+
.collect();
225+
AmfValue::Object(id, promoted, class)
226+
}
227+
AmfValue::AMF3(inner) => AmfValue::AMF3(promote_rc(inner)),
228+
AmfValue::VectorObject(id, values, name, fixed) => {
229+
let promoted = values.into_iter().map(promote_rc).collect();
230+
AmfValue::VectorObject(id, promoted, name, fixed)
231+
}
232+
AmfValue::Dictionary(id, body, weak_keys) => {
233+
let promoted = body
234+
.into_iter()
235+
.map(|(k, v)| (promote_rc(k), promote_rc(v)))
236+
.collect();
237+
AmfValue::Dictionary(id, promoted, weak_keys)
238+
}
239+
AmfValue::Custom(custom_elements, regular_elements, class) => {
240+
let promoted_custom = custom_elements
241+
.into_iter()
242+
.map(|e| Element::new(e.name, promote_rc(e.value)))
243+
.collect();
244+
let promoted_regular = regular_elements
245+
.into_iter()
246+
.map(|e| Element::new(e.name, promote_rc(e.value)))
247+
.collect();
248+
AmfValue::Custom(promoted_custom, promoted_regular, class)
249+
}
250+
leaf => leaf,
251+
}
252+
}
253+
254+
fn promote_rc(rc: Rc<AmfValue>) -> Rc<AmfValue> {
255+
let owned = Rc::try_unwrap(rc).unwrap_or_else(|shared| (*shared).clone());
256+
Rc::new(promote_dense_ecma_to_strict_array(owned))
257+
}
258+
196259
fn alias_to_class<'gc>(
197260
activation: &mut Activation<'_, 'gc>,
198261
alias: AvmString<'gc>,

core/src/avm2/globals/flash/net/local_connection.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::avm2::amf::serialize_value;
1+
use crate::avm2::amf::{promote_dense_ecma_to_strict_array, serialize_value};
22
use crate::avm2::error::{
33
Error2004Type, make_error_2004, make_error_2082, make_error_2083, make_error_2085,
44
};
@@ -51,10 +51,11 @@ pub fn send<'gc>(
5151

5252
let mut amf_arguments = Vec::with_capacity(args.len() - 2);
5353
for arg in &args[2..] {
54-
amf_arguments.push(
55-
serialize_value(activation, *arg, AMFVersion::AMF0, &mut Default::default())
56-
.unwrap_or(AmfValue::Undefined),
57-
);
54+
let value = serialize_value(activation, *arg, AMFVersion::AMF0, &mut Default::default())
55+
.unwrap_or(AmfValue::Undefined);
56+
// Real Flash sends dense AS3 `Array` arguments as `StrictArray` over
57+
// the LocalConnection wire (#16381).
58+
amf_arguments.push(promote_dense_ecma_to_strict_array(value));
5859
}
5960

6061
if let Some(local_connection) = this.as_local_connection_object() {

core/src/avm2/globals/flash/net/net_connection.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::avm2::amf::serialize_value;
1+
use crate::avm2::amf::{promote_dense_ecma_to_strict_array, serialize_value};
22
use crate::avm2::error::make_error_2126;
33
pub use crate::avm2::object::net_connection_allocator;
44
use crate::avm2::parameters::ParametersExt;
@@ -284,7 +284,9 @@ pub fn call<'gc>(
284284
for arg in &args[2..] {
285285
if let Some(value) = serialize_value(activation, *arg, AMFVersion::AMF0, &mut object_table)
286286
{
287-
arguments.push(Rc::new(value));
287+
// Real Flash sends dense AS3 `Array` arguments as `StrictArray` on
288+
// the NetConnection.call wire (#16381).
289+
arguments.push(Rc::new(promote_dense_ecma_to_strict_array(value)));
288290
}
289291
}
290292

@@ -341,6 +343,7 @@ pub fn add_header<'gc>(
341343
AMFVersion::AMF0,
342344
&mut Default::default(),
343345
)
346+
.map(promote_dense_ecma_to_strict_array)
344347
.unwrap_or(AMFValue::Null);
345348

346349
if let Some(handle) = connection.handle() {

0 commit comments

Comments
 (0)