Ruffle AVM1 AMF0 严格数组序列化测试指南:用 NetConnection 验证网络上的真实字节流

📅 发布时间:2026/9/13 11:40:20
Ruffle AVM1 AMF0 严格数组序列化测试指南:用 NetConnection 验证网络上的真实字节流
Ruffle AVM1 AMF0 严格数组序列化测试指南用 NetConnection 验证网络上的真实字节流【免费下载链接】ruffleA Flash Player emulator written in Rust项目地址: https://gitcode.com/GitHub_Trending/ru/ruffle本指南围绕 Ruffle 仓库中tests/tests/swfs/avm1/amf_strict_array_serialization/测试目录展开讲解如何通过本地 HTTP 服务器抓取NetConnection.call实际发出的 AMF0 载荷逐字节验证严格数组Strict Array与混合数组ECMA Array的序列化规则。读完本文你将掌握该测试的完整运行流程Flash Player 与 Ruffle Desktop 两种方式、Linux 下的 FlashPlayerTrust 网络授权配置以及 Ruffle 在 core/src/avm1/amf.rs 中实现数组序列化的底层判定逻辑并学会如何借助test.toml与output.txt解读测试期望。一、测试背景为什么需要严格数组序列化测试AMF0Action Message Format 0是 Flash 时代NetConnection、LocalConnection、SharedObject等网络与本地存储 API 共同使用的二进制数据格式。AMF0 对数组有两种截然不同的编码方式StrictArray严格数组0x0A带一个 4 字节的array-length随后是连续的元素适用于索引连续、没有额外自定义属性的纯数组ECMAArray混合数组0x08带一个 4 字节的associative-count和数量不定的键值对适用于带有非数字自定义属性的混合数组。真实 Flash Player 在选择编码方式时有一套微妙的行为密集数组输出 StrictArray含自定义字符串属性的数组输出 ECMAArray而稀疏数组则会把空洞用undefined补齐。这套行为此前在 Ruffle 中存在兼容性缺陷因此仓库特意建立了这个回归测试目录把 AVM1 侧的多种数组形态一次性通过NetConnection.call发送出去再用本地服务器捕获原始字节作为判定依据。二、测试目录结构总览该测试目录位于 tests/tests/swfs/avm1/amf_strict_array_serialization/共包含 6 个文件文件作用README.md运行说明本文讲解的关联文档server.py本地 HTTP 服务器捕获并打印 POST 请求的十六进制字节test.asActionScript 1 源码构造 8 种数组形态并通过NetConnection.call发送test.swf由test.as编译出的 SWFAVM1SWF 版本 8test.tomlRuffle 回归测试框架的配置文件output.txtRuffle 运行时的期望输出内含完整的 AMF0 字节载荷三、如何运行该测试3.1 启动本地抓包服务器server.py是一个基于 Python 标准库http.server的极简 HTTP 服务器监听0.0.0.0:8000。它只处理POST请求读取Content-Length指定的请求体后将每个字节格式化为大写十六进制并打印到标准输出request self.rfile.read(int(self.headers[Content-Length])) request_hex , .join([f{byte:02X} for byte in request]) print(f Body: [{request_hex}])从 server.py 的实现可以看出服务器会同时打印请求的URL、Method、Mime-Type即Content-Type头和十六进制Body。运行方式python3 server.py # 输出Running server on port 8000...3.2 运行 test.swfREADME 指出服务器启动后需要在 Flash Player 或 Ruffle Desktop 播放器中运行test.swf。测试脚本执行时会通过NetConnection向http://localhost:8000/发起一次POST请求详见test.as第 55–65 行var nc new NetConnection(); nc.connect(http://localhost:8000/); var responder new Object(); responder.onResult function(res) { /* Pass */ }; nc.call(test.avm1, responder, denseArray, sparseArray, mixedArray, fakeArray, throwingArray, superFakeArray, demotedArray, protoOnlyArray);服务器端随即打印出Navigator::fetch:段包含完整的 AMF0 请求载荷。3.3 Linux 下 Flash Player 的网络授权配置如果使用官方 Flash Player而非 Ruffle其沙箱默认禁止 SWF 发起网络连接。README 给出了 Linux 上的解决方案创建全局信任配置文件/etc/adobe/FlashPlayerTrust/test.cfg内容写入test.swf所在路径的任意祖先目录/ancestor/of/swf/path例如test.swf位于/home/username/tests/amf_strict_array_serialization/test.swf则写入/home/username/即可。/etc/adobe/FlashPlayerTrust/目录下的.cfg文件是 Flash Player 在 Linux 上的标准信任机制其中的每个路径都会被加入白名单位于该路径之下的 SWF 可以无提示地访问网络。使用 Ruffle 时无需此步骤Ruffle 的回归测试框架通过log_fetch选项直接记录网络请求不走 Flash Player 的沙箱信任机制。3.4 通过 Ruffle 测试框架自动运行除了手动运行该测试还直接接入了 Ruffle 的 SWF 回归测试框架。其 test.toml 配置如下num_ticks 10 log_fetch true [[compilers]] type Rascal target test.swf scripts [test.as] swf_version 8各字段含义依据 tests/README.md 与 tests/framework/src/options.rsnum_ticks 10运行 SWF 的帧数。NetConnection.call是异步操作需要足够的 tick 让网络请求完成并输出Navigator::fetch日志这也是该配置中唯一必填字段。log_fetch true将所有网络请求URL、Method、Mime-Type、Body纳入测试输出。这正是output.txt中能够出现完整 AMF0 字节流的原因——在 tests/framework/src/runner.rs 中log_fetch为 true 时框架会挂接日志收集器test.options.log_fetch.then(|| log.clone())。[[compilers]]段指定用 Rascal 编译器将test.as编译为test.swf目标 SWF 版本为 8AVM1 特性集。框架会将实际输出与 output.txt 逐行比对作为回归断言。四、测试用例8 种数组形态的构造test.as 精心构造了 8 个对象覆盖 AVM1 中数组类型判定的全部边界情况变量构造方式预期编码denseArraypush(dense_0, dense_1)典型密集数组 → StrictArray0x0A长度 2sparseArray仅赋值下标 0 与 5稀疏数组 → StrictArray0x0A长度扩展为 6空洞用undefined0x06填充mixedArraypush(mixed_0)后再加自定义属性custom_prop含非数字键 → ECMAArray0x08附带custom_prop键值对fakeArray普通Object手动设置0与length非原生数组 → 匿名 Object0x03逐属性序列化throwingArray在1与length上注册抛异常的 gettergetter 属性 → 序列化为undefined0x06不触发求值superFakeArray通过__proto__与__initializeNative()模拟super() 升级内部判定的 Array长度 6→ StrictArraydemotedArray将__proto__/__constructor__改成Object被降级为非数组 → 匿名 ObjectprotoOnlyArray仅把__proto__指向Array.prototype的普通对象非原生数组 → 匿名 Object0x03其中superFakeArray用例还在第 38 行输出一条关键 trace 用于判定运行时行为trace(Did super() upgrade instance? (superFakeArray.length 6 ? YES : NO));对应output.txt第一行Did super() upgrade instance? YES验证了 Ruffle 的 AVM1 对象系统在super()调用后会正确地把实例内部升级为原生数组。五、期望输出解读逐字节看懂 AMF0 载荷output.txt是框架比对基准其核心是Navigator::fetch段打印出的完整请求Navigator::fetch: URL: http://localhost:8000/ Method: POST Mime-Type: application/x-amf Body: [00, 00, 00, 00, 00, 01, 00, 09, 74, 65, 73, 74, 2E, 61, 76, 6D, 31, ...]前 18 字节是 AMF0 的invoke消息头远程方法调用格式4 字节消息大小 2 字节version0x0001 2 字节header-count0x0000 2 字节message-count0x0001 2 字节target-uri长度0x0009 ASCII 字符串test.avm1随后是response-uri与消息体。消息体中 8 个参数依次编码为denseArray→0x0A 00000002StrictArray长度 2两个 UTF 字符串dense_0、dense_1sparseArray→0x0A 00000006StrictArray长度被扩展到 6sparse_0之后连续出现 4 个0x06undefined填充空洞末尾sparse_5mixedArray→0x08 00000001ECMAArray先有 1 个元素mixed_0随后键值对custom_prop/custom_value最后0x00 0x0009结束标记fakeArray→0x03匿名Object含0→fake_0与length→1.0AMF0 数字为 8 字节 IEEE 754 双精度throwingArray→0x09 0x0A 00000002StrictArraysafe_0之后是0x06即抛异常的 getter1被序列化为undefined且length的 getter 未被求值superFakeArray→0x0A 00000001StrictArray仅含super_fake_0length被重置为 1 后序列化demotedArray→0x0A 00000002StrictArray两个元素AVM1 内部仍判定为数组编码不依赖__constructor__protoOnlyArray→0x03匿名Object含0与length两个属性。这份字节流与core/src/avm1/amf.rs的序列化分支一一对应是验证 Ruffle 与 Flash Player 行为一致性的黄金标准。六、源码实现Ruffle 如何判定数组并选择编码output.txt中的每个字节都对应 core/src/avm1/amf.rs 的具体实现逻辑。该文件同时服务于NetConnection、LocalConnection与SharedObject三类 API见 core/src/avm1/globals/netconnection.rs、core/src/avm1/globals/local_connection.rs、core/src/avm1/globals/shared_object.rs 对serialize/deserialize_value/recursive_serialize的调用。6.1 顶层分派serializeserialize首先按值类型分派Undefined/Null/Bool/Number/String直接映射为对应 AMF0 标量MovieClip与显示对象被序列化为undefined。对于Object则按优先级检查是否为显示对象 →undefined是否为原生数组NativeObject::Array→ 进入serialize_array这是本文的核心路径是否为 XML 节点 → AMF0 XML0x0F是否为 Date → AMF0 Date0x0B否则 → 匿名对象或带类名的 TypedObject逐属性序列化。6.2 数组分支StrictArray 与 ECMAArray 的判定serialize_array是严格数组行为的核心实现其判定规则为长度扩展遍历所有键凡是能被解析为usize的数字键若index 1超过当前length则扩展序列化长度对应sparseArray输出长度 6 的行为自定义属性检测任何完全非数字的键都会使has_custom_properties true编码选择含自定义属性 →AmfValue::ECMAArray0x08对应mixedArray纯数字键 →AmfValue::StrictArray0x0A并显式用undefined填充空洞以保持索引连续对应sparseArray中 4 个0x06的来源。源码注释还特别说明了两点与 Flash 的兼容细节属性按插入顺序的反序收集keys.into_iter().rev()以保证与 Flash Player 的字段顺序一致数字键即使超出length也会扩展数组序列化长度。6.3 Getter 与异常处理为什么throwingArray不崩溃recursive_serializecore/src/avm1/amf.rs在收集对象属性时对虚拟属性getter一律不执行求值直接序列化为undefinedValue::Undefined并在注释中说明Flash never evaluates getters during AMF serialization。因此test.as中注册了抛异常 getter 的1和length属性不会触发异常throwingArray才得以完整输出——这是该测试用例与实现代码互相印证的关键点。在serialize_value_to_writer中函数对象则被整体跳过Flash entirely skips functions。6.4 类名解析object_class_nameobject_class_name负责 TypedObject 的类名解析先查找对象自身的constructor属性若为虚拟属性则放弃再回退到隐藏的__constructor__属性最后通过context.avm1.get_class_name_by_constructor查找注册的类名。这与fakeArray、protoOnlyArray被编码为匿名Object0x03无类名的行为吻合——普通Object没有可映射到注册类的构造函数。6.5 反序列化路径deserialize_valuedeserialize_value是响应方向的对称实现ECMAArray先以len为参数构造 AVM1 数组再按元素名是否为整数决定走set_element还是define_valueStrictArray则以元素数量构造数组后逐下标赋值。二者都会把对象登记进reference_cache以支持 AMF0 的对象引用0x09。该函数在NetConnection的send_callback中被调用core/src/avm1/globals/netconnection.rs响应结果经deserialize_value还原后回调给responder.onResult——这正是test.as中responder对象的作用。七、对比参考AVM2 侧的 AMF0 序列化作为对照AVM2 也有独立的 AMF 序列化实现 core/src/avm2/amf.rs被 flash/net/net_connection.rs、flash/net/local_connection.rs、flash/net/shared_object.rs 与 flash/net/socket.rs 复用。AVM2 侧有一个 AVM1 不存在的规则——整数在 AMF0 中必须转换为 NumberValue::Integer(i) if amf_version AMFVersion::AMF0 AmfValue::Number(i as f64)。此外flash.net.ObjectEncoding类core/src/avm2/globals/flash/net/ObjectEncoding.as定义了AMF0 0常量网络套接字 API 会根据ObjectEncoding在 AMF0/AMF3 之间切换见 core/src/avm2/globals/flash/net/socket.rs 中ObjectEncoding::Amf0 AMFVersion::AMF0及 AMF0 消息头长度8的分支。本测试聚焦 AVM1SWF 版本 8但理解两侧共享的flash_lsoAMF0 编解码基础有助于读者把amf_strict_array_serialization的结论推广到整个 Ruffle 网络栈。八、运行环境与适用前提手动验证方式需要 Python 3标准库即可启动server.py播放端可以是 Flash Player需按 README 配置/etc/adobe/FlashPlayerTrust/test.cfg或 Ruffle Desktop 播放器。自动化回归方式通过 Ruffle 测试框架运行该目录下的测试由test.toml的log_fetch true与num_ticks 10驱动以output.txt为期望输出比对。测试性质output.txt中的Navigator::fetch日志URL、Method、Mime-Type、Body是 Ruffle 测试框架对网络请求的记录仅在log_fetch开启时产出并非 Flash Player 的原始抓包结果Flash Player 下的真实行为需按 3.3 节配置信任后通过server.py自行验证。通过本文的流程你可以复现 Ruffle 对 AVM1 数组 AMF0 序列化的完整验证闭环从test.as构造边界用例、到server.py抓取真实字节、再到output.txt与amf.rs实现逐条对照最终确认严格数组与混合数组在网络层的行为与 Flash Player 一致。【免费下载链接】ruffleA Flash Player emulator written in Rust项目地址: https://gitcode.com/GitHub_Trending/ru/ruffle创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考