Skip to content

Commit 6aa45b9

Browse files
committed
avm1: Mirror AS3 Array → StrictArray promotion for AVM1 NetConnection/LocalConnection (#16381)
Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
1 parent 4b5d980 commit 6aa45b9

4 files changed

Lines changed: 60 additions & 6 deletions

File tree

core/src/avm1/globals/local_connection.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::avm1::globals::shared_object::{deserialize_value, serialize};
66
use crate::avm1::property_decl::{DeclContext, StaticDeclarations, SystemClass};
77
use crate::avm1::{ActivationIdentifier, ExecutionReason, NativeObject, Object, Value};
88
use crate::avm1_stub;
9+
use crate::avm2::amf::promote_dense_ecma_to_strict_array;
910
use crate::context::UpdateContext;
1011
use crate::display_object::TDisplayObject;
1112
use crate::local_connection::{LocalConnectionHandle, LocalConnections};
@@ -226,7 +227,11 @@ pub fn send<'gc>(
226227

227228
let mut amf_arguments = Vec::with_capacity(args.len() - 2);
228229
for arg in &args[2..] {
229-
amf_arguments.push(serialize(activation, *arg));
230+
// Real Flash sends dense AS1/AS2 `Array` arguments as `StrictArray`
231+
// over the LocalConnection wire (#16381). Mirrors the same promotion
232+
// done in `NetConnection.call`.
233+
let value = serialize(activation, *arg);
234+
amf_arguments.push(promote_dense_ecma_to_strict_array(value));
230235
}
231236

232237
activation.context.local_connections.send(

core/src/avm1/globals/netconnection.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::avm1::{
44
Activation, ActivationIdentifier, Error, ExecutionReason, NativeObject, Object, Value,
55
};
66
use crate::avm1_stub;
7+
use crate::avm2::amf::promote_dense_ecma_to_strict_array;
78
use crate::context::UpdateContext;
89
use crate::net_connection::{NetConnectionHandle, NetConnections, ResponderCallback};
910
use crate::string::AvmString;
@@ -274,7 +275,12 @@ fn call<'gc>(
274275

275276
if args.len() > 2 {
276277
for arg in &args[2..] {
277-
arguments.push(Rc::new(serialize(activation, *arg)));
278+
// Real Flash sends dense AS1/AS2 `Array` arguments as `StrictArray`
279+
// on the NetConnection.call wire (#16381). Nested dense arrays
280+
// produced by `recursive_serialize`'s `ECMAArray` path are promoted
281+
// here as well.
282+
let value = serialize(activation, *arg);
283+
arguments.push(Rc::new(promote_dense_ecma_to_strict_array(value)));
278284
}
279285
}
280286

core/src/avm1/globals/shared_object.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::display_object::TDisplayObject;
55
use crate::string::AvmString;
66
use flash_lso::amf0::read::AMF0Decoder;
77
use flash_lso::amf0::writer::{Amf0Writer, CacheKey, ObjWriter};
8-
use flash_lso::types::{Lso, ObjectId, Reference, Value as AmfValue};
8+
use flash_lso::types::{Element, Lso, ObjectId, Reference, Value as AmfValue};
99
use gc_arena::{Collect, Gc};
1010
use ruffle_macros::istr;
1111
use std::borrow::Cow;
@@ -86,13 +86,56 @@ pub fn serialize<'gc>(activation: &mut Activation<'_, 'gc>, value: Value<'gc>) -
8686
Value::Number(number) => AmfValue::Number(number),
8787
Value::String(string) => AmfValue::String(string.to_string()),
8888
Value::Object(object) => {
89-
let lso = new_lso(activation, "root", object);
90-
AmfValue::Object(ObjectId::INVALID, lso.into_iter().collect(), None)
89+
if let NativeObject::Array(_) = object.native() {
90+
// AS1/AS2 `Array` values are serialized using the AMF0 array
91+
// markers so that Flash Remoting / LocalConnection endpoints
92+
// round-trip them as arrays rather than as anonymous objects
93+
// with `"0"`/`"1"`/... keys. Callers that wire the result
94+
// onto a path Flash sends as `StrictArray` (NetConnection.call
95+
// / LocalConnection.send argument trees, #16381) post-process
96+
// with `crate::avm2::amf::promote_dense_ecma_to_strict_array`.
97+
serialize_array(activation, object)
98+
} else {
99+
let lso = new_lso(activation, "root", object);
100+
AmfValue::Object(ObjectId::INVALID, lso.into_iter().collect(), None)
101+
}
91102
}
92103
Value::MovieClip(_) => AmfValue::Undefined,
93104
}
94105
}
95106

107+
/// Serialize an AS1/AS2 `Array` into the AMF0 array form. Dense indices fill
108+
/// the first slot of the returned `ECMAArray`; non-numeric or out-of-range
109+
/// keys go into the associative part. The wrapping `ECMAArray` matches what
110+
/// flash-lso's `ObjWriter::array`/`ArrayWriter::commit` produces for nested
111+
/// arrays inside `recursive_serialize`, so behavior is uniform between
112+
/// top-level and nested `Array` serialization.
113+
fn serialize_array<'gc>(activation: &mut Activation<'_, 'gc>, array: Object<'gc>) -> AmfValue {
114+
let length = array.length(activation).unwrap_or(0).max(0) as u32;
115+
let mut dense: Vec<std::rc::Rc<AmfValue>> = (0..length)
116+
.map(|_| std::rc::Rc::new(AmfValue::Undefined))
117+
.collect();
118+
let mut associative: Vec<Element> = Vec::new();
119+
// Reversed to match Flash Player enumeration order, mirroring
120+
// `recursive_serialize`.
121+
for key in array.get_keys(activation, false).into_iter().rev() {
122+
let key_str = key.to_utf8_lossy();
123+
let value = array
124+
.get(key, activation)
125+
.map(|v| serialize(activation, v))
126+
.unwrap_or(AmfValue::Undefined);
127+
match key_str.parse::<u32>() {
128+
Ok(idx) if idx < length => {
129+
dense[idx as usize] = std::rc::Rc::new(value);
130+
}
131+
_ => {
132+
associative.push(Element::new(key_str.to_string(), std::rc::Rc::new(value)));
133+
}
134+
}
135+
}
136+
AmfValue::ECMAArray(ObjectId::INVALID, dense, associative, length)
137+
}
138+
96139
/// Serialize an Object and any children to a JSON object
97140
fn recursive_serialize<'gc>(
98141
activation: &mut Activation<'_, 'gc>,

core/src/avm2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ macro_rules! avm_debug {
4141
}
4242

4343
pub mod activation;
44-
mod amf;
44+
pub(crate) mod amf;
4545
pub mod api_version;
4646
mod array;
4747
pub mod bytearray;

0 commit comments

Comments
 (0)