@@ -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+
196259fn alias_to_class < ' gc > (
197260 activation : & mut Activation < ' _ , ' gc > ,
198261 alias : AvmString < ' gc > ,
0 commit comments