diff --git a/core/src/avm1/globals/local_connection.rs b/core/src/avm1/globals/local_connection.rs index f67edaa8a55d..e487cc613623 100644 --- a/core/src/avm1/globals/local_connection.rs +++ b/core/src/avm1/globals/local_connection.rs @@ -6,6 +6,7 @@ use crate::avm1::globals::shared_object::{deserialize_value, serialize}; use crate::avm1::property_decl::{DeclContext, StaticDeclarations, SystemClass}; use crate::avm1::{ActivationIdentifier, ExecutionReason, NativeObject, Object, Value}; use crate::avm1_stub; +use crate::avm2::amf::promote_dense_ecma_to_strict_array; use crate::context::UpdateContext; use crate::display_object::TDisplayObject; use crate::local_connection::{LocalConnectionHandle, LocalConnections}; @@ -226,7 +227,11 @@ pub fn send<'gc>( let mut amf_arguments = Vec::with_capacity(args.len() - 2); for arg in &args[2..] { - amf_arguments.push(serialize(activation, *arg)); + // Real Flash sends dense AS1/AS2 `Array` arguments as `StrictArray` + // over the LocalConnection wire (#16381). Mirrors the same promotion + // done in `NetConnection.call`. + let value = serialize(activation, *arg); + amf_arguments.push(promote_dense_ecma_to_strict_array(value)); } activation.context.local_connections.send( diff --git a/core/src/avm1/globals/netconnection.rs b/core/src/avm1/globals/netconnection.rs index 2919cbe52bb3..2c4fc08f2b19 100644 --- a/core/src/avm1/globals/netconnection.rs +++ b/core/src/avm1/globals/netconnection.rs @@ -4,6 +4,7 @@ use crate::avm1::{ Activation, ActivationIdentifier, Error, ExecutionReason, NativeObject, Object, Value, }; use crate::avm1_stub; +use crate::avm2::amf::promote_dense_ecma_to_strict_array; use crate::context::UpdateContext; use crate::net_connection::{NetConnectionHandle, NetConnections, ResponderCallback}; use crate::string::AvmString; @@ -274,7 +275,12 @@ fn call<'gc>( if args.len() > 2 { for arg in &args[2..] { - arguments.push(Rc::new(serialize(activation, *arg))); + // Real Flash sends dense AS1/AS2 `Array` arguments as `StrictArray` + // on the NetConnection.call wire (#16381). Nested dense arrays + // produced by `recursive_serialize`'s `ECMAArray` path are promoted + // here as well. + let value = serialize(activation, *arg); + arguments.push(Rc::new(promote_dense_ecma_to_strict_array(value))); } } diff --git a/core/src/avm1/globals/shared_object.rs b/core/src/avm1/globals/shared_object.rs index 183726f9ec91..66462084b007 100644 --- a/core/src/avm1/globals/shared_object.rs +++ b/core/src/avm1/globals/shared_object.rs @@ -5,11 +5,12 @@ use crate::display_object::TDisplayObject; use crate::string::AvmString; use flash_lso::amf0::read::AMF0Decoder; use flash_lso::amf0::writer::{Amf0Writer, CacheKey, ObjWriter}; -use flash_lso::types::{Lso, ObjectId, Reference, Value as AmfValue}; +use flash_lso::types::{Element, Lso, ObjectId, Reference, Value as AmfValue}; use gc_arena::{Collect, Gc}; use ruffle_macros::istr; use std::borrow::Cow; use std::collections::BTreeMap; +use std::rc::Rc; #[derive(Default, Clone, Collect)] #[collect(require_static)] @@ -86,13 +87,61 @@ pub fn serialize<'gc>(activation: &mut Activation<'_, 'gc>, value: Value<'gc>) - Value::Number(number) => AmfValue::Number(number), Value::String(string) => AmfValue::String(string.to_string()), Value::Object(object) => { - let lso = new_lso(activation, "root", object); - AmfValue::Object(ObjectId::INVALID, lso.into_iter().collect(), None) + if let NativeObject::Array(_) = object.native() { + // AS1/AS2 `Array` values are serialized using the AMF0 array + // markers so that Flash Remoting / LocalConnection endpoints + // round-trip them as arrays rather than as anonymous objects + // with `"0"`/`"1"`/... keys. Callers that wire the result + // onto a path Flash sends as `StrictArray` (NetConnection.call + // / LocalConnection.send argument trees, #16381) post-process + // with `crate::avm2::amf::promote_dense_ecma_to_strict_array`. + serialize_array(activation, object) + } else { + let lso = new_lso(activation, "root", object); + AmfValue::Object(ObjectId::INVALID, lso.into_iter().collect(), None) + } } Value::MovieClip(_) => AmfValue::Undefined, } } +/// Serialize an AS1/AS2 `Array` into the AMF0 array form, matching real Flash's +/// wire encoding for `NetConnection.call` / `LocalConnection.send` arguments +/// (#16381). Keys that line up with the enumeration index (i.e. the array +/// starts with a dense `0..n` prefix) fill the `ECMAArray`'s dense slot; +/// every later key falls into the associative slot. This mirrors AVM2's +/// `serialize_value` Array branch and, combined with `promote_dense_ecma_to_strict_array` +/// at NetConnection/LocalConnection call sites, yields `StrictArray` for pure +/// dense arrays and an insertion-ordered `ECMAArray` for arrays with custom +/// properties. +fn serialize_array<'gc>(activation: &mut Activation<'_, 'gc>, array: Object<'gc>) -> AmfValue { + let length = array.length(activation).unwrap_or(0).max(0) as u32; + let mut dense: Vec> = Vec::new(); + let mut associative: Vec = Vec::new(); + // `PropertyMap::iter` yields most-recently-added first, so `.rev()` + // restores Flash's enumeration (insertion) order. Preserving this order + // is what lets the associative slot serialize as the exact byte sequence + // real Flash sends for mixed arrays. + for (i, key) in array + .get_keys(activation, false) + .into_iter() + .rev() + .enumerate() + { + let name = key.to_utf8_lossy().to_string(); + let value = array + .get(key, activation) + .map(|v| serialize(activation, v)) + .unwrap_or(AmfValue::Undefined); + if name == i.to_string() { + dense.push(Rc::new(value)); + } else { + associative.push(Element::new(name, Rc::new(value))); + } + } + AmfValue::ECMAArray(ObjectId::INVALID, dense, associative, length) +} + /// Serialize an Object and any children to a JSON object fn recursive_serialize<'gc>( activation: &mut Activation<'_, 'gc>, @@ -198,6 +247,31 @@ pub fn deserialize_value<'gc>( Value::Undefined } } + AmfValue::StrictArray(_, values) => { + // Real Flash uses `StrictArray` (AMF0 marker `0x0A`) for dense AS3 + // `Array`s sent over `NetConnection.call` / `LocalConnection.send` + // (#16381), so the AVM1 receiver has to reconstruct an `Array` + // here rather than falling through to `Undefined`. + let array_constructor = activation.prototypes().array_constructor; + if let Ok(Value::Object(obj)) = + array_constructor.construct(activation, &[(values.len() as i32).into()]) + { + let v: Value<'gc> = obj.into(); + + if let Some(reference) = lso.as_reference(val) { + reference_cache.insert(reference, v); + } + + for (i, item) in values.iter().enumerate() { + let value = deserialize_value(activation, item, lso, reference_cache); + obj.set_element(activation, i as i32, value).unwrap(); + } + + v + } else { + Value::Undefined + } + } AmfValue::Object(_, elements, _) => { // Deserialize Object let obj = Object::new( diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 40083888b156..43b8fe77be58 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -41,7 +41,7 @@ macro_rules! avm_debug { } pub mod activation; -mod amf; +pub(crate) mod amf; pub mod api_version; mod array; pub mod bytearray; diff --git a/core/src/avm2/amf.rs b/core/src/avm2/amf.rs index 26a58d877ebe..52425685d965 100644 --- a/core/src/avm2/amf.rs +++ b/core/src/avm2/amf.rs @@ -51,25 +51,25 @@ pub fn serialize_value<'gc>( recursive_serialize(activation, o, &mut values, None, amf_version, object_table) .unwrap(); - if amf_version == AMFVersion::AMF3 { - let mut dense = vec![]; - let mut sparse = vec![]; - // ActionScript `Array`s can have non-number properties, and these properties - // are confirmed and tested to also be serialized, so do not limit the values - // iterated over by the length of the internal array data. - for (i, elem) in values.into_iter().enumerate() { - if elem.name == i.to_string() { - dense.push(elem.value.clone()); - } else { - sparse.push(elem); - } + // ActionScript `Array`s can have non-number properties, and these properties + // are confirmed and tested to also be serialized, so do not limit the values + // iterated over by the length of the internal array data. + let mut dense = vec![]; + let mut sparse = vec![]; + for (i, elem) in values.into_iter().enumerate() { + if elem.name == i.to_string() { + dense.push(elem.value.clone()); + } else { + sparse.push(elem); } - - Some(AmfValue::ECMAArray(ObjectId::INVALID, dense, sparse, len)) - } else { - // TODO: is this right? - Some(AmfValue::ECMAArray(ObjectId::INVALID, vec![], values, len)) } + + // AS3 `Array` is serialized as `ECMAArray` for both AMF0 and AMF3. + // Callers that need `StrictArray` semantics for dense AS3 `Array`s + // (e.g. `NetConnection.call` / `LocalConnection.send` argument + // encoding, see #16381) post-process the returned tree with + // `promote_dense_ecma_to_strict_array`. + Some(AmfValue::ECMAArray(ObjectId::INVALID, dense, sparse, len)) } else if let Some(vec) = o.as_vector_storage() { let val_type = vec.value_type(); if val_type == Some(activation.avm2().class_defs().int) { @@ -183,6 +183,79 @@ pub fn serialize_value<'gc>( } } +/// Recursively rewrite dense `ECMAArray` nodes in `value` as `StrictArray`. +/// +/// Real Flash encodes the argument tree for `NetConnection.call` and +/// `LocalConnection.send` with `StrictArray` (marker `0x0A`) for every dense +/// AS3 `Array`, while `ByteArray.writeObject` and `Socket.writeObject` keep +/// the `ECMAArray` (marker `0x08`) encoding for those same arrays. The default +/// `serialize_value` output uses `ECMAArray` to match the latter; the Flash +/// Remoting code paths call this helper to convert the tree into the form +/// real Flash sends on the wire (#16381). +/// +/// An `ECMAArray` is treated as dense and promoted when its sparse portion is +/// empty and its declared length matches the dense portion exactly. All other +/// nodes are walked so that nested dense arrays inside objects, vectors, +/// dictionaries and AMF3 wrappers are promoted too. +pub fn promote_dense_ecma_to_strict_array(value: AmfValue) -> AmfValue { + match value { + AmfValue::ECMAArray(_, dense, sparse, len) + if sparse.is_empty() && dense.len() as u32 == len => + { + let promoted = dense.into_iter().map(promote_rc).collect(); + AmfValue::StrictArray(ObjectId::INVALID, promoted) + } + AmfValue::ECMAArray(id, dense, sparse, len) => { + let promoted_dense = dense.into_iter().map(promote_rc).collect(); + let promoted_sparse = sparse + .into_iter() + .map(|e| Element::new(e.name, promote_rc(e.value))) + .collect(); + AmfValue::ECMAArray(id, promoted_dense, promoted_sparse, len) + } + AmfValue::StrictArray(id, values) => { + let promoted = values.into_iter().map(promote_rc).collect(); + AmfValue::StrictArray(id, promoted) + } + AmfValue::Object(id, elements, class) => { + let promoted = elements + .into_iter() + .map(|e| Element::new(e.name, promote_rc(e.value))) + .collect(); + AmfValue::Object(id, promoted, class) + } + AmfValue::AMF3(inner) => AmfValue::AMF3(promote_rc(inner)), + AmfValue::VectorObject(id, values, name, fixed) => { + let promoted = values.into_iter().map(promote_rc).collect(); + AmfValue::VectorObject(id, promoted, name, fixed) + } + AmfValue::Dictionary(id, body, weak_keys) => { + let promoted = body + .into_iter() + .map(|(k, v)| (promote_rc(k), promote_rc(v))) + .collect(); + AmfValue::Dictionary(id, promoted, weak_keys) + } + AmfValue::Custom(custom_elements, regular_elements, class) => { + let promoted_custom = custom_elements + .into_iter() + .map(|e| Element::new(e.name, promote_rc(e.value))) + .collect(); + let promoted_regular = regular_elements + .into_iter() + .map(|e| Element::new(e.name, promote_rc(e.value))) + .collect(); + AmfValue::Custom(promoted_custom, promoted_regular, class) + } + leaf => leaf, + } +} + +fn promote_rc(rc: Rc) -> Rc { + let owned = Rc::try_unwrap(rc).unwrap_or_else(|shared| (*shared).clone()); + Rc::new(promote_dense_ecma_to_strict_array(owned)) +} + fn alias_to_class<'gc>( activation: &mut Activation<'_, 'gc>, alias: AvmString<'gc>, diff --git a/core/src/avm2/globals/flash/net/local_connection.rs b/core/src/avm2/globals/flash/net/local_connection.rs index 704ff76bcc68..7a51f00be182 100644 --- a/core/src/avm2/globals/flash/net/local_connection.rs +++ b/core/src/avm2/globals/flash/net/local_connection.rs @@ -1,4 +1,4 @@ -use crate::avm2::amf::serialize_value; +use crate::avm2::amf::{promote_dense_ecma_to_strict_array, serialize_value}; use crate::avm2::error::{ Error2004Type, make_error_2004, make_error_2082, make_error_2083, make_error_2085, }; @@ -51,10 +51,11 @@ pub fn send<'gc>( let mut amf_arguments = Vec::with_capacity(args.len() - 2); for arg in &args[2..] { - amf_arguments.push( - serialize_value(activation, *arg, AMFVersion::AMF0, &mut Default::default()) - .unwrap_or(AmfValue::Undefined), - ); + let value = serialize_value(activation, *arg, AMFVersion::AMF0, &mut Default::default()) + .unwrap_or(AmfValue::Undefined); + // Real Flash sends dense AS3 `Array` arguments as `StrictArray` over + // the LocalConnection wire (#16381). + amf_arguments.push(promote_dense_ecma_to_strict_array(value)); } if let Some(local_connection) = this.as_local_connection_object() { diff --git a/core/src/avm2/globals/flash/net/net_connection.rs b/core/src/avm2/globals/flash/net/net_connection.rs index 665aabac8cf6..598b7ddc0f3d 100644 --- a/core/src/avm2/globals/flash/net/net_connection.rs +++ b/core/src/avm2/globals/flash/net/net_connection.rs @@ -1,4 +1,4 @@ -use crate::avm2::amf::serialize_value; +use crate::avm2::amf::{promote_dense_ecma_to_strict_array, serialize_value}; use crate::avm2::error::make_error_2126; pub use crate::avm2::object::net_connection_allocator; use crate::avm2::parameters::ParametersExt; @@ -284,7 +284,9 @@ pub fn call<'gc>( for arg in &args[2..] { if let Some(value) = serialize_value(activation, *arg, AMFVersion::AMF0, &mut object_table) { - arguments.push(Rc::new(value)); + // Real Flash sends dense AS3 `Array` arguments as `StrictArray` on + // the NetConnection.call wire (#16381). + arguments.push(Rc::new(promote_dense_ecma_to_strict_array(value))); } } @@ -341,6 +343,7 @@ pub fn add_header<'gc>( AMFVersion::AMF0, &mut Default::default(), ) + .map(promote_dense_ecma_to_strict_array) .unwrap_or(AMFValue::Null); if let Some(handle) = connection.handle() { diff --git a/tests/tests/swfs/avm1/netconnection_serialize_arrays/README.md b/tests/tests/swfs/avm1/netconnection_serialize_arrays/README.md new file mode 100644 index 000000000000..e355bad2e0de --- /dev/null +++ b/tests/tests/swfs/avm1/netconnection_serialize_arrays/README.md @@ -0,0 +1,12 @@ + # Running the test + + +To verify the actual data sent over the network, run 'server.py' from this directory. Then, run 'test.swf' in either Flash Player or the Ruffle Desktop player. + +When running under flash player, you'll need to allow the SWF to make network connections. On Linux, this can be done by creating the file `/etc/adobe/FlashPlayerTrust/test.cfg` with the following contents: + +``` +/ancestor/of/swf/path +``` + +where `ancestor/of/swf/path` is any path that's an ancestor of the path of `test.swf` (e.g. `/home/username/`) diff --git a/tests/tests/swfs/avm1/netconnection_serialize_arrays/output.txt b/tests/tests/swfs/avm1/netconnection_serialize_arrays/output.txt new file mode 100644 index 000000000000..3a2cbb3a9732 --- /dev/null +++ b/tests/tests/swfs/avm1/netconnection_serialize_arrays/output.txt @@ -0,0 +1,6 @@ +--- Testing NetConnection Array Serialization --- +Navigator::fetch: + URL: http://localhost:8000/ + Method: POST + Mime-Type: application/x-amf + Body: [00, 00, 00, 00, 00, 01, 00, 0B, 74, 65, 73, 74, 2E, 61, 72, 72, 61, 79, 73, 00, 02, 2F, 31, 00, 00, 00, B3, 0A, 00, 00, 00, 03, 0A, 00, 00, 00, 02, 02, 00, 06, 72, 65, 61, 6C, 5F, 30, 02, 00, 06, 72, 65, 61, 6C, 5F, 31, 03, 00, 01, 30, 02, 00, 06, 66, 61, 6B, 65, 5F, 30, 00, 01, 31, 02, 00, 06, 66, 61, 6B, 65, 5F, 31, 00, 06, 6C, 65, 6E, 67, 74, 68, 00, 40, 00, 00, 00, 00, 00, 00, 00, 00, 00, 09, 08, 00, 00, 00, 02, 00, 06, 61, 5F, 70, 72, 6F, 70, 02, 00, 07, 76, 61, 6C, 75, 65, 5F, 61, 00, 01, 30, 02, 00, 07, 6D, 69, 78, 65, 64, 5F, 30, 00, 06, 6D, 5F, 70, 72, 6F, 70, 02, 00, 07, 76, 61, 6C, 75, 65, 5F, 6D, 00, 01, 31, 02, 00, 07, 6D, 69, 78, 65, 64, 5F, 31, 00, 06, 7A, 5F, 70, 72, 6F, 70, 02, 00, 07, 76, 61, 6C, 75, 65, 5F, 7A, 00, 06, 62, 5F, 70, 72, 6F, 70, 02, 00, 07, 76, 61, 6C, 75, 65, 5F, 62, 00, 00, 09] diff --git a/tests/tests/swfs/avm1/netconnection_serialize_arrays/server.py b/tests/tests/swfs/avm1/netconnection_serialize_arrays/server.py new file mode 100644 index 000000000000..3344dd8424f7 --- /dev/null +++ b/tests/tests/swfs/avm1/netconnection_serialize_arrays/server.py @@ -0,0 +1,29 @@ +from http.server import HTTPServer, BaseHTTPRequestHandler + +class MyHandler(BaseHTTPRequestHandler): + def do_POST(self): + print("") + print("Navigator::fetch:") + print(f" URL: http://localhost:8000{self.path}") + print(" Method: POST") + print(f" Mime-Type: {self.headers.get('Content-Type')}") + + request = self.rfile.read(int(self.headers['Content-Length'])) + # Format as uppercase hex bytes + request_hex = ", ".join([f"{byte:02X}" for byte in request]) + + print(f" Body: [{request_hex}]") + print("") + + self.send_response(200) + self.end_headers() + self.wfile.write(b"") + +def run(server_class=HTTPServer, handler_class=MyHandler): + server_address = ('', 8000) + httpd = server_class(server_address, handler_class) + print("Running server on port 8000...") + httpd.serve_forever() + +if __name__ == '__main__': + run() diff --git a/tests/tests/swfs/avm1/netconnection_serialize_arrays/test.as b/tests/tests/swfs/avm1/netconnection_serialize_arrays/test.as new file mode 100644 index 000000000000..c8ba0665c701 --- /dev/null +++ b/tests/tests/swfs/avm1/netconnection_serialize_arrays/test.as @@ -0,0 +1,32 @@ +var nc = new NetConnection(); +nc.connect("http://localhost:8000/"); + +// Callback function +var responder = new Object(); +responder.onResult = function(result) { + trace("Received result"); +}; + +// 1. Genuine Array +var realArray = new Array(); +realArray[0] = "real_0"; +realArray[1] = "real_1"; + +// 2. Fake Array +var fakeArray = new Object(); +fakeArray[0] = "fake_0"; +fakeArray[1] = "fake_1"; +fakeArray.length = 2; + +// 3. Mixed Array (Genuine Array with a custom string property) +var mixedArray = new Array(); +mixedArray["a_prop"] = "value_a"; +mixedArray[0] = "mixed_0"; +mixedArray["m_prop"] = "value_m"; +mixedArray[1] = "mixed_1"; +mixedArray["z_prop"] = "value_z"; +mixedArray["b_prop"] = "value_b"; + +trace("--- Testing NetConnection Array Serialization ---"); +// Pass all three arrays to the server +nc.call("test.arrays", responder, realArray, fakeArray, mixedArray); diff --git a/tests/tests/swfs/avm1/netconnection_serialize_arrays/test.swf b/tests/tests/swfs/avm1/netconnection_serialize_arrays/test.swf new file mode 100644 index 000000000000..bb30705eb8a5 Binary files /dev/null and b/tests/tests/swfs/avm1/netconnection_serialize_arrays/test.swf differ diff --git a/tests/tests/swfs/avm1/netconnection_serialize_arrays/test.toml b/tests/tests/swfs/avm1/netconnection_serialize_arrays/test.toml new file mode 100644 index 000000000000..6400f5dd1316 --- /dev/null +++ b/tests/tests/swfs/avm1/netconnection_serialize_arrays/test.toml @@ -0,0 +1,8 @@ +num_ticks = 10 +log_fetch = true + +[[compilers]] +type = "Rascal" +target = "test.swf" +scripts = ["test.as"] +swf_version = 8 diff --git a/tests/tests/swfs/avm2/netconnection_serialize_arrays/README.md b/tests/tests/swfs/avm2/netconnection_serialize_arrays/README.md new file mode 100644 index 000000000000..38eade9b382e --- /dev/null +++ b/tests/tests/swfs/avm2/netconnection_serialize_arrays/README.md @@ -0,0 +1,19 @@ +# Running the test + +To verify the actual data sent over the network, run 'server.py' from this directory. Then, run 'test.swf' in either Flash Player or the Ruffle Desktop player. + +When running under flash player, you'll need to allow the SWF to make network connections. On Linux, this can be done by creating the file `/etc/adobe/FlashPlayerTrust/test.cfg` with the following contents: + +``` +/ancestor/of/swf/path +``` + +where `ancestor/of/swf/path` is any path that's an ancestor of the path of `test.swf` (e.g. `/home/username/`) + +# Recompiling test.swf + +`Test.as` is an AS3 (AVM2) document class. Recompile with the Apache Flex SDK (see CONTRIBUTING.md#apache-flex-sdk): + +``` +mxmlc -o test.swf -debug Test.as +``` diff --git a/tests/tests/swfs/avm2/netconnection_serialize_arrays/Test.as b/tests/tests/swfs/avm2/netconnection_serialize_arrays/Test.as new file mode 100644 index 000000000000..0ae341d2377e --- /dev/null +++ b/tests/tests/swfs/avm2/netconnection_serialize_arrays/Test.as @@ -0,0 +1,35 @@ +package { + import flash.display.MovieClip; + import flash.net.NetConnection; + import flash.net.ObjectEncoding; + import flash.net.Responder; + + public class Test extends MovieClip { + public function Test() { + var nc:NetConnection = new NetConnection(); + + // This test targets the AMF0 wire format. At time of writing Ruffle + // doesn't support AMF3 properly, and the StrictArray fix (#16381) is + // on the AMF0 path, so pin the encoding to AMF0 like the sibling + // netconnection_send_remote test does. + nc.objectEncoding = ObjectEncoding.AMF0; + nc.connect("http://localhost:8000/"); + + var responder:Responder = new Responder(onResult, null); + + // A genuine dense AS3 Array. Real Flash sends this as an AMF0 + // StrictArray (marker 0x0A); before #16381 Ruffle emitted an + // ECMAArray (marker 0x08) with "0"/"1" string keys instead. + var realArray:Array = new Array(); + realArray[0] = "real_0"; + realArray[1] = "real_1"; + + trace("--- Testing NetConnection Array Serialization ---"); + nc.call("test.arrays", responder, realArray); + } + + private function onResult(result:*):void { + trace("Received result"); + } + } +} diff --git a/tests/tests/swfs/avm2/netconnection_serialize_arrays/output.txt b/tests/tests/swfs/avm2/netconnection_serialize_arrays/output.txt new file mode 100644 index 000000000000..97129e7484de --- /dev/null +++ b/tests/tests/swfs/avm2/netconnection_serialize_arrays/output.txt @@ -0,0 +1,6 @@ +--- Testing NetConnection Array Serialization --- +Navigator::fetch: + URL: http://localhost:8000/ + Method: POST + Mime-Type: application/x-amf + Body: [00, 00, 00, 00, 00, 01, 00, 0B, 74, 65, 73, 74, 2E, 61, 72, 72, 61, 79, 73, 00, 02, 2F, 31, 00, 00, 00, 1C, 0A, 00, 00, 00, 01, 0A, 00, 00, 00, 02, 02, 00, 06, 72, 65, 61, 6C, 5F, 30, 02, 00, 06, 72, 65, 61, 6C, 5F, 31] diff --git a/tests/tests/swfs/avm2/netconnection_serialize_arrays/server.py b/tests/tests/swfs/avm2/netconnection_serialize_arrays/server.py new file mode 100644 index 000000000000..3344dd8424f7 --- /dev/null +++ b/tests/tests/swfs/avm2/netconnection_serialize_arrays/server.py @@ -0,0 +1,29 @@ +from http.server import HTTPServer, BaseHTTPRequestHandler + +class MyHandler(BaseHTTPRequestHandler): + def do_POST(self): + print("") + print("Navigator::fetch:") + print(f" URL: http://localhost:8000{self.path}") + print(" Method: POST") + print(f" Mime-Type: {self.headers.get('Content-Type')}") + + request = self.rfile.read(int(self.headers['Content-Length'])) + # Format as uppercase hex bytes + request_hex = ", ".join([f"{byte:02X}" for byte in request]) + + print(f" Body: [{request_hex}]") + print("") + + self.send_response(200) + self.end_headers() + self.wfile.write(b"") + +def run(server_class=HTTPServer, handler_class=MyHandler): + server_address = ('', 8000) + httpd = server_class(server_address, handler_class) + print("Running server on port 8000...") + httpd.serve_forever() + +if __name__ == '__main__': + run() diff --git a/tests/tests/swfs/avm2/netconnection_serialize_arrays/test.swf b/tests/tests/swfs/avm2/netconnection_serialize_arrays/test.swf new file mode 100644 index 000000000000..0409e2577f47 Binary files /dev/null and b/tests/tests/swfs/avm2/netconnection_serialize_arrays/test.swf differ diff --git a/tests/tests/swfs/avm2/netconnection_serialize_arrays/test.toml b/tests/tests/swfs/avm2/netconnection_serialize_arrays/test.toml new file mode 100644 index 000000000000..bfb882672871 --- /dev/null +++ b/tests/tests/swfs/avm2/netconnection_serialize_arrays/test.toml @@ -0,0 +1,5 @@ +# This test verifies the bytes sent over the network, see README.md. +# AS3 sends a dense Array as an AMF0 StrictArray over NetConnection.call (#16381). + +num_ticks = 10 +log_fetch = true