From 287ca8801a52f3cb14a885711ebe4da8c4c52dec Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sat, 25 Nov 2023 19:19:44 -0500 Subject: [PATCH] avm2: Implement AIR playerglobal versioning This builds on our existing playerglobal versioning support to add in AIR versioning. We closely follow the avmplus implementation: * When an SWF is loaded, we chose either a FlashPlayer or AIR APIVersion for its SWF version, based on our configured player runtime. * When loading playerglobals, we look at the player runtime. In AIR mode, we map FlashPlayer-versioned definitions to the closest AIR version. This ensures that all runtime APIVersions are in the same series (either AIR or FlashPlayer). In FlashPlayer mode, all AIR-versioned definitions get mapped to VM_INTERNAL, hiding them from user code. Part of our existing api versioning code was implemented incorrectly. Within playerglobals, we need to treat all unmarked namespaces as VM_INTERNAL - this allows things like playerglobal script initializer "initproperty" opcodes to see any VM_INTERNAL AIR definitions (when we run under FlashPlayer mode). Previously, we were using AllVersions, which would result in those VM_INTERNAL definitions being hidden from other playerglobal code, which is not correct. Using this support, I've added a stub for the AIR-only 'flash.net.DatagramSocket'. I've also extended the test framework with a new 'player_options.runtime' config option, which can be set to "AIR" or "FlashPlayer" to configure the test runtime mode. I've also added two new tests: * 'air_hidden_lookup' runs under the FlashPlayer runtime, and verifies that a list of classes (currently just "DatagramSocket" are inacessible). * 'air_datagram_socket', which uses `player_options.runtime = "AIR"` to construct an instance of `flash.net.DatagramSocket`. We can extend this test once we implement more of `DatagramSocket` With this commit, we have all of the needed infrastructure to start implementing and testing AIR-only classes and methods. --- Cargo.lock | 1 + core/Cargo.toml | 1 + core/src/avm2.rs | 12 +- core/src/avm2/api_version.rs | 166 +++++++++++++++--- core/src/avm2/globals.rs | 2 +- .../avm2/globals/flash/net/DatagramSocket.as | 6 + core/src/avm2/globals/globals.as | 1 + core/src/avm2/namespace.rs | 40 ++++- core/src/avm2/script.rs | 6 +- core/src/player.rs | 14 +- desktop/src/player.rs | 1 + tests/README.md | 1 + tests/framework/src/options.rs | 5 +- .../swfs/avm2/air_datagram_socket/Test.as | 18 ++ .../swfs/avm2/air_datagram_socket/output.txt | 1 + .../swfs/avm2/air_datagram_socket/test.fla | Bin 0 -> 3884 bytes .../swfs/avm2/air_datagram_socket/test.swf | Bin 0 -> 474 bytes .../swfs/avm2/air_datagram_socket/test.toml | 4 + .../tests/swfs/avm2/air_hidden_lookup/Test.as | 26 +++ .../swfs/avm2/air_hidden_lookup/output.txt | 1 + .../swfs/avm2/air_hidden_lookup/test.fla | Bin 0 -> 4180 bytes .../swfs/avm2/air_hidden_lookup/test.swf | Bin 0 -> 663 bytes .../swfs/avm2/air_hidden_lookup/test.toml | 1 + 23 files changed, 267 insertions(+), 40 deletions(-) create mode 100644 core/src/avm2/globals/flash/net/DatagramSocket.as create mode 100755 tests/tests/swfs/avm2/air_datagram_socket/Test.as create mode 100644 tests/tests/swfs/avm2/air_datagram_socket/output.txt create mode 100755 tests/tests/swfs/avm2/air_datagram_socket/test.fla create mode 100755 tests/tests/swfs/avm2/air_datagram_socket/test.swf create mode 100644 tests/tests/swfs/avm2/air_datagram_socket/test.toml create mode 100755 tests/tests/swfs/avm2/air_hidden_lookup/Test.as create mode 100644 tests/tests/swfs/avm2/air_hidden_lookup/output.txt create mode 100755 tests/tests/swfs/avm2/air_hidden_lookup/test.fla create mode 100755 tests/tests/swfs/avm2/air_hidden_lookup/test.swf create mode 100644 tests/tests/swfs/avm2/air_hidden_lookup/test.toml diff --git a/Cargo.lock b/Cargo.lock index 3bbb5fcca..1c5e41eb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4263,6 +4263,7 @@ dependencies = [ "egui", "egui_extras", "encoding_rs", + "enum-map", "enumset", "flash-lso", "flate2", diff --git a/core/Cargo.toml b/core/Cargo.toml index 26031bf07..52e4bfd6d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -58,6 +58,7 @@ flv-rs = { path = "../flv" } async-channel = "2.1.1" jpegxr = { git = "https://github.com/ruffle-rs/jpegxr", branch = "ruffle", optional = true } image = { version = "0.24.7", default-features = false, features = ["tiff", "dxt"] } +enum-map = "2.7.3" [target.'cfg(not(target_family = "wasm"))'.dependencies.futures] version = "0.3.29" diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 6c0a1552f..5a17db01b 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -11,6 +11,7 @@ use crate::context::{GcContext, UpdateContext}; use crate::display_object::{DisplayObject, DisplayObjectWeak, TDisplayObject}; use crate::string::AvmString; use crate::tag_utils::SwfMovie; +use crate::PlayerRuntime; use fnv::FnvHashMap; use gc_arena::{Collect, GcCell, Mutation}; @@ -92,6 +93,10 @@ pub struct Avm2<'gc> { /// The Flash Player version we're emulating. player_version: u8, + /// The player runtime we're emulating + #[collect(require_static)] + pub player_runtime: PlayerRuntime, + /// Values currently present on the operand stack. stack: Vec>, @@ -180,7 +185,11 @@ pub struct Avm2<'gc> { impl<'gc> Avm2<'gc> { /// Construct a new AVM interpreter. - pub fn new(context: &mut GcContext<'_, 'gc>, player_version: u8) -> Self { + pub fn new( + context: &mut GcContext<'_, 'gc>, + player_version: u8, + player_runtime: PlayerRuntime, + ) -> Self { let playerglobals_domain = Domain::uninitialized_domain(context.gc_context, None); let stage_domain = Domain::uninitialized_domain(context.gc_context, Some(playerglobals_domain)); @@ -191,6 +200,7 @@ impl<'gc> Avm2<'gc> { Self { player_version, + player_runtime, stack: Vec::new(), scope_stack: Vec::new(), call_stack: GcCell::new(context.gc_context, CallStack::new()), diff --git a/core/src/avm2/api_version.rs b/core/src/avm2/api_version.rs index 4b82f0874..74c08ed02 100644 --- a/core/src/avm2/api_version.rs +++ b/core/src/avm2/api_version.rs @@ -1,7 +1,10 @@ +use std::sync::OnceLock; + +use crate::PlayerRuntime; +use enum_map::{enum_map, Enum, EnumMap}; + // Based on https://github.com/adobe/avmplus/blob/master/core/api-versions.h -// FIXME - if we ever add in AIR emulation, then implement the 'version series' logic. -// For now, it's fine to just compare Flash player version against air versions directly. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, FromPrimitive)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, FromPrimitive, Enum)] #[allow(non_camel_case_types)] pub enum ApiVersion { AllVersions = 0, @@ -59,37 +62,142 @@ pub enum ApiVersion { VM_INTERNAL = 52, } +static TRANSFER_TABLE: OnceLock> = OnceLock::new(); + impl ApiVersion { - pub fn from_swf_version(val: u8) -> Option { - match val { + pub fn to_valid_playerglobals_version(self, runtime: PlayerRuntime) -> ApiVersion { + // This maps an ApiVersion from our playerglobals SWF to the closest valid version, + // based on the active runtime. + // + // If our runtime is AIR, then we leave ApiVersion::AIR_* unchanged, and map + // ApiVersion::FP_* to the closest AIR version. This has the effect of exposing + // API versioned with an AIR-specific version, and also exposing all of the normal FP + // APIs that were included in that AIR release. + // + // If our runtime is FlashPlayer, then we leave ApiVersion::FP_* unchanged, and + // map ApiVersion::AIR_* to VM_INTERNAL. This hides all AIR-specific APIs when + // running in FlashPlayer. + // + // See https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/api-versions.cpp#L63 + let active_series = TRANSFER_TABLE.get_or_init(|| { + enum_map! { + ApiVersion::AllVersions => (ApiVersion::AllVersions, ApiVersion::AllVersions), + ApiVersion::AIR_1_0 => (ApiVersion::AIR_1_0, ApiVersion::VM_INTERNAL), + ApiVersion::FP_10_0 => (ApiVersion::AIR_1_5, ApiVersion::FP_10_0), + ApiVersion::AIR_1_5 => (ApiVersion::AIR_1_5, ApiVersion::VM_INTERNAL), + ApiVersion::AIR_1_5_1 => (ApiVersion::AIR_1_5_1, ApiVersion::VM_INTERNAL), + ApiVersion::FP_10_0_32 => (ApiVersion::AIR_1_5_2, ApiVersion::FP_10_0_32), + ApiVersion::AIR_1_5_2 => (ApiVersion::AIR_1_5_2, ApiVersion::VM_INTERNAL), + ApiVersion::FP_10_1 => (ApiVersion::AIR_2_0, ApiVersion::FP_10_1), + ApiVersion::AIR_2_0 => (ApiVersion::AIR_2_0, ApiVersion::VM_INTERNAL), + ApiVersion::AIR_2_5 => (ApiVersion::AIR_2_5, ApiVersion::VM_INTERNAL), + ApiVersion::FP_10_2 => (ApiVersion::AIR_2_6, ApiVersion::FP_10_2), + ApiVersion::AIR_2_6 => (ApiVersion::AIR_2_6, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_12 => (ApiVersion::SWF_12, ApiVersion::SWF_12), + ApiVersion::AIR_2_7 => (ApiVersion::AIR_2_7, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_13 => (ApiVersion::AIR_3_0, ApiVersion::SWF_13), + ApiVersion::AIR_3_0 => (ApiVersion::AIR_3_0, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_14 => (ApiVersion::AIR_3_1, ApiVersion::SWF_14), + ApiVersion::AIR_3_1 => (ApiVersion::AIR_3_1, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_15 => (ApiVersion::AIR_3_2, ApiVersion::SWF_15), + ApiVersion::AIR_3_2 => (ApiVersion::AIR_3_2, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_16 => (ApiVersion::AIR_3_3, ApiVersion::SWF_16), + ApiVersion::AIR_3_3 => (ApiVersion::AIR_3_3, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_17 => (ApiVersion::AIR_3_4, ApiVersion::SWF_17), + ApiVersion::AIR_3_4 => (ApiVersion::AIR_3_4, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_18 => (ApiVersion::AIR_3_5, ApiVersion::SWF_18), + ApiVersion::AIR_3_5 => (ApiVersion::AIR_3_5, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_19 => (ApiVersion::AIR_3_6, ApiVersion::SWF_19), + ApiVersion::AIR_3_6 => (ApiVersion::AIR_3_6, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_20 => (ApiVersion::AIR_3_7, ApiVersion::SWF_20), + ApiVersion::AIR_3_7 => (ApiVersion::AIR_3_7, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_21 => (ApiVersion::AIR_3_8, ApiVersion::SWF_21), + ApiVersion::AIR_3_8 => (ApiVersion::AIR_3_8, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_22 => (ApiVersion::AIR_3_9, ApiVersion::SWF_22), + ApiVersion::AIR_3_9 => (ApiVersion::AIR_3_9, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_23 => (ApiVersion::AIR_4_0, ApiVersion::SWF_23), + ApiVersion::AIR_4_0 => (ApiVersion::AIR_4_0, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_24 => (ApiVersion::AIR_13_0, ApiVersion::SWF_24), + ApiVersion::AIR_13_0 => (ApiVersion::AIR_13_0, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_25 => (ApiVersion::AIR_14_0, ApiVersion::SWF_25), + ApiVersion::AIR_14_0 => (ApiVersion::AIR_14_0, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_26 => (ApiVersion::AIR_15_0, ApiVersion::SWF_26), + ApiVersion::AIR_15_0 => (ApiVersion::AIR_15_0, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_27 => (ApiVersion::AIR_16_0, ApiVersion::SWF_27), + ApiVersion::AIR_16_0 => (ApiVersion::AIR_16_0, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_28 => (ApiVersion::AIR_17_0, ApiVersion::SWF_28), + ApiVersion::AIR_17_0 => (ApiVersion::AIR_17_0, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_29 => (ApiVersion::AIR_18_0, ApiVersion::SWF_29), + ApiVersion::AIR_18_0 => (ApiVersion::AIR_18_0, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_30 => (ApiVersion::AIR_19_0, ApiVersion::SWF_30), + ApiVersion::AIR_19_0 => (ApiVersion::AIR_19_0, ApiVersion::VM_INTERNAL), + ApiVersion::SWF_31 => (ApiVersion::AIR_20_0, ApiVersion::SWF_31), + ApiVersion::AIR_20_0 => (ApiVersion::AIR_20_0, ApiVersion::VM_INTERNAL), + ApiVersion::VM_INTERNAL => (ApiVersion::VM_INTERNAL, ApiVersion::VM_INTERNAL), + } + })[self]; + + match runtime { + PlayerRuntime::AIR => active_series.0, + PlayerRuntime::FlashPlayer => active_series.1, + } + } + + pub fn from_swf_version(val: u8, runtime: PlayerRuntime) -> Option { + // Based on this table: https://github.com/ruffle-rs/ruffle/wiki/SWF-version-chart + match (val, runtime) { // There's no specific entry for SWF 9 in avmplus, // so map it to the lowest entry. - 9 => Some(ApiVersion::AllVersions), - 10 => Some(ApiVersion::FP_10_1), - 11 => Some(ApiVersion::FP_10_2), - 12 => Some(ApiVersion::SWF_12), - 13 => Some(ApiVersion::SWF_13), - 14 => Some(ApiVersion::SWF_14), - 15 => Some(ApiVersion::SWF_15), - 16 => Some(ApiVersion::SWF_16), - 17 => Some(ApiVersion::SWF_17), - 18 => Some(ApiVersion::SWF_18), - 19 => Some(ApiVersion::SWF_19), - 20 => Some(ApiVersion::SWF_20), - 21 => Some(ApiVersion::SWF_21), - 22 => Some(ApiVersion::SWF_22), - 23 => Some(ApiVersion::SWF_23), - 24 => Some(ApiVersion::SWF_24), - 25 => Some(ApiVersion::SWF_25), - 26 => Some(ApiVersion::SWF_26), - 27 => Some(ApiVersion::SWF_27), - 28 => Some(ApiVersion::SWF_28), - 29 => Some(ApiVersion::SWF_29), - 30 => Some(ApiVersion::SWF_30), - 31 => Some(ApiVersion::SWF_31), + (9, _) => Some(ApiVersion::AllVersions), + (10, PlayerRuntime::FlashPlayer) => Some(ApiVersion::FP_10_1), + (10, PlayerRuntime::AIR) => Some(ApiVersion::AIR_2_0), + (11, PlayerRuntime::FlashPlayer) => Some(ApiVersion::FP_10_2), + (11, PlayerRuntime::AIR) => Some(ApiVersion::AIR_2_6), + (12, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_12), + (12, PlayerRuntime::AIR) => Some(ApiVersion::AIR_2_7), + (13, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_13), + (13, PlayerRuntime::AIR) => Some(ApiVersion::AIR_3_0), + (14, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_14), + (14, PlayerRuntime::AIR) => Some(ApiVersion::AIR_3_1), + (15, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_15), + (15, PlayerRuntime::AIR) => Some(ApiVersion::AIR_3_2), + (16, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_16), + (16, PlayerRuntime::AIR) => Some(ApiVersion::AIR_3_3), + (17, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_17), + (17, PlayerRuntime::AIR) => Some(ApiVersion::AIR_3_4), + (18, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_18), + (18, PlayerRuntime::AIR) => Some(ApiVersion::AIR_3_5), + (19, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_19), + (19, PlayerRuntime::AIR) => Some(ApiVersion::AIR_3_6), + (20, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_20), + (20, PlayerRuntime::AIR) => Some(ApiVersion::AIR_3_7), + (21, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_21), + (21, PlayerRuntime::AIR) => Some(ApiVersion::AIR_3_8), + (22, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_22), + (22, PlayerRuntime::AIR) => Some(ApiVersion::AIR_3_9), + (23, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_23), + (23, PlayerRuntime::AIR) => Some(ApiVersion::AIR_4_0), + (24, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_24), + (24, PlayerRuntime::AIR) => Some(ApiVersion::AIR_13_0), + (25, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_25), + (25, PlayerRuntime::AIR) => Some(ApiVersion::AIR_14_0), + (26, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_26), + (26, PlayerRuntime::AIR) => Some(ApiVersion::AIR_15_0), + (27, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_27), + (27, PlayerRuntime::AIR) => Some(ApiVersion::AIR_16_0), + (28, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_28), + (28, PlayerRuntime::AIR) => Some(ApiVersion::AIR_17_0), + (29, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_29), + (29, PlayerRuntime::AIR) => Some(ApiVersion::AIR_18_0), + (30, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_30), + (30, PlayerRuntime::AIR) => Some(ApiVersion::AIR_19_0), + (31, PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_31), + (31, PlayerRuntime::AIR) => Some(ApiVersion::AIR_20_0), + // We haven't yet created entries from higher versions - just map them // to the highest non-VM_INTERNAL version. - _ if val > 31 => Some(ApiVersion::SWF_31), + (32.., PlayerRuntime::FlashPlayer) => Some(ApiVersion::SWF_31), + (32.., PlayerRuntime::AIR) => Some(ApiVersion::AIR_20_0), _ => None, } } diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 013666d02..8353512d1 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -702,7 +702,7 @@ fn load_playerglobal<'gc>( // Lookup with the highest version, so we we see all defined classes here let ns = Namespace::package($package, ApiVersion::VM_INTERNAL, &mut activation.borrow_gc()); let name = QName::new(ns, $class_name); - let class_object = activation.domain().get_defined_value(activation, name)?; + let class_object = activation.domain().get_defined_value(activation, name).unwrap_or_else(|e| panic!("Failed to lookup {name:?}: {e:?}")); let class_object = class_object.as_object().unwrap().as_class_object().unwrap(); let sc = activation.avm2().system_classes.as_mut().unwrap(); sc.$field = class_object; diff --git a/core/src/avm2/globals/flash/net/DatagramSocket.as b/core/src/avm2/globals/flash/net/DatagramSocket.as new file mode 100644 index 000000000..215101cba --- /dev/null +++ b/core/src/avm2/globals/flash/net/DatagramSocket.as @@ -0,0 +1,6 @@ +package flash.net { + [API("668")] // AIR 2.0 + public class DatagramSocket { + + } +} \ No newline at end of file diff --git a/core/src/avm2/globals/globals.as b/core/src/avm2/globals/globals.as index 8861ecc7a..fb085fe7d 100644 --- a/core/src/avm2/globals/globals.as +++ b/core/src/avm2/globals/globals.as @@ -254,6 +254,7 @@ include "flash/media/VideoStreamSettings.as" include "flash/external/ExternalInterface.as" include "flash/net.as" +include "flash/net/DatagramSocket.as" include "flash/net/FileFilter.as" include "flash/net/FileReference.as" include "flash/net/FileReferenceList.as" diff --git a/core/src/avm2/namespace.rs b/core/src/avm2/namespace.rs index eae8b727f..e4821ca4f 100644 --- a/core/src/avm2/namespace.rs +++ b/core/src/avm2/namespace.rs @@ -107,9 +107,38 @@ impl<'gc> Namespace<'gc> { ))); } - // FIXME - what other versioned urls are there? - let is_versioned_url = |url: AvmAtom<'gc>| url.as_wstr().is_empty(); - + // FIXME - AvmCore gets this from an external source. I'm not exactly sure + // what the contents it, but it's probably all 'flash.*', 'air.*', etc. namespaces + // This is only ever used when parsing our playerglobals, so we just treat everything + // as versioned for now. As a result, any intra-playerglobal *references* that lack + // an explicit version marker will be treated as ApiVersion::VM_INTERNAL. + // The only exceptions are the 'AS3' ("http://adobe.com/AS3/2006/builtin") + // and "flash_proxy" (b"http://www.adobe.com/2006/actionscript/flash/proxy") namespaces. + // These are used by user code, and are not given version markers in playerglobals + // by the ASC compiler. As a result, we do not treat them as versioned, so that + // references from within playerglobals will use ApiVersion::AllVersions; + // + // For example, consider the AIR-only class `flash.net.DatagramSocket`. The class + // definition has version marker corresponding to an AIR-only version - when running + // the Flash Player runtime, we will map this to VM_INTERNAL in `ApiVersion::to_valid_playerglobals_version` + // (which hides it from user code). However, the playerglobal will still try to initialize this class via: + // + // ``` + // initproperty QName(PackageNamespace("flash.net"),"DatagramSocket") + // ``` + // + // This is a namespace without a version marker (the compiler only ever generates version + // markers in definitions, not references). As a result, we will treat this as a VM_INTERNAL + // which will allow `initproperty` to see the `flash.net.DatagramSocket` class definition, + // even when running as the FlashPlayer (not AIR) runtime. + // + // Outside of playerglobals, we'll tag all namespaces with a version based on the SWF version. + // This is always less than VM_INTERNAL, so AIR-only classes will be correctly hidden outside + // of playerglobals when using the FlashPlayer runtime. + let is_versioned_url = |url: AvmAtom<'gc>| { + url.as_wstr() != b"http://adobe.com/AS3/2006/builtin" + && url.as_wstr() != b"http://www.adobe.com/2006/actionscript/flash/proxy" + }; let is_public = matches!( abc_namespace, AbcNamespace::Namespace(_) | AbcNamespace::Package(_) @@ -137,6 +166,11 @@ impl<'gc> Namespace<'gc> { if !has_version_mark && is_public && is_versioned_url(namespace_name) { api_version = ApiVersion::VM_INTERNAL; } + // In avmplus, this conversion is done later in in 'getValidApiVersion' + // However, there's no reason to hold on to invalid API versions for the + // current active series (player runtime), so let's just do the conversion immediately. + api_version = + api_version.to_valid_playerglobals_version(context.avm2.player_runtime); } else if is_public { api_version = translation_unit.api_version(context.avm2); }; diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index e1d891b57..8951313f0 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -16,6 +16,7 @@ use crate::avm2::{Avm2, Error}; use crate::context::{GcContext, UpdateContext}; use crate::string::{AvmAtom, AvmString}; use crate::tag_utils::SwfMovie; +use crate::PlayerRuntime; use gc_arena::{Collect, Gc, GcCell, Mutation}; use std::cell::Ref; use std::mem::drop; @@ -135,7 +136,10 @@ impl<'gc> TranslationUnit<'gc> { pub fn api_version(self, avm2: &Avm2<'gc>) -> ApiVersion { if self.domain().is_playerglobals_domain(avm2) { // FIXME: get this from the player version we're emulating - ApiVersion::SWF_31 + match avm2.player_runtime { + PlayerRuntime::FlashPlayer => ApiVersion::SWF_31, + PlayerRuntime::AIR => ApiVersion::AIR_20_0, + } } else { avm2.root_api_version } diff --git a/core/src/player.rs b/core/src/player.rs index 25e54dd01..ccaa41c13 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -57,6 +57,7 @@ use ruffle_render::commands::CommandList; use ruffle_render::quality::StageQuality; use ruffle_render::transform::TransformStack; use ruffle_video::backend::VideoBackend; +use serde::Deserialize; use std::cell::RefCell; use std::collections::{HashMap, VecDeque}; use std::ops::DerefMut; @@ -392,8 +393,11 @@ impl Player { self.mutate_with_update_context(|context| { if context.swf.is_action_script_3() { - context.avm2.root_api_version = ApiVersion::from_swf_version(context.swf.version()) - .unwrap_or_else(|| panic!("Unknown SWF version {}", context.swf.version())); + context.avm2.root_api_version = ApiVersion::from_swf_version( + context.swf.version(), + context.avm2.player_runtime, + ) + .unwrap_or_else(|| panic!("Unknown SWF version {}", context.swf.version())); } context.stage.set_movie_size( @@ -2432,6 +2436,7 @@ impl PlayerBuilder { fn create_gc_root<'gc>( gc_context: &'gc gc_arena::Mutation<'gc>, player_version: u8, + player_runtime: PlayerRuntime, fullscreen: bool, fake_movie: Arc, external_interface_providers: Vec>, @@ -2452,7 +2457,7 @@ impl PlayerBuilder { audio_manager: AudioManager::new(), action_queue: ActionQueue::new(), avm1: Avm1::new(&mut init, player_version), - avm2: Avm2::new(&mut init, player_version), + avm2: Avm2::new(&mut init, player_version, player_runtime), interner, current_context_menu: None, drag_object: None, @@ -2572,6 +2577,7 @@ impl PlayerBuilder { Self::create_gc_root( gc_context, player_version, + self.player_runtime, self.fullscreen, fake_movie.clone(), self.external_interface_providers, @@ -2682,7 +2688,7 @@ fn run_mouse_pick<'gc>( } #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] -#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Deserialize)] pub enum PlayerRuntime { #[default] FlashPlayer, diff --git a/desktop/src/player.rs b/desktop/src/player.rs index 9b66e4dbb..53ebe3f49 100644 --- a/desktop/src/player.rs +++ b/desktop/src/player.rs @@ -166,6 +166,7 @@ impl ActivePlayer { .with_spoofed_url(opt.spoof_url.clone().map(|url| url.to_string())) .with_page_url(opt.spoof_url.clone().map(|url| url.to_string())) .with_player_version(Some(opt.player_version)) + .with_player_runtime(opt.player_runtime) .with_frame_rate(opt.frame_rate); let player = builder.build(); diff --git a/tests/README.md b/tests/README.md index 7807e037c..fe263c3a3 100644 --- a/tests/README.md +++ b/tests/README.md @@ -40,6 +40,7 @@ viewport_dimensions = { width = 100, height = 100, scale_factor = 1 } # The size with_renderer = { optional = false, sample_count = 4, exclude_warp = false } # If this test requires a renderer to run. Optional will enable the renderer where available. with_audio = false # If this test requires an audio backend to run. with_video = false # If this test requires a video decoder backend to run. +runtime = "AIR" # The runtime to emulate ("FlashPlayer" or "AIR"). Defaults to "FlashPlayer" # A list of image comparisons to perform during the test. This block is repeatable infinitely, as long as each name is unique. # The comparison part of a test is optional and only runs when `imgtests` feature is enabled diff --git a/tests/framework/src/options.rs b/tests/framework/src/options.rs index 956837ac7..3aaa19dbd 100644 --- a/tests/framework/src/options.rs +++ b/tests/framework/src/options.rs @@ -7,7 +7,7 @@ use approx::assert_relative_eq; use image::ImageOutputFormat; use regex::Regex; use ruffle_core::tag_utils::SwfMovie; -use ruffle_core::{PlayerBuilder, ViewportDimensions}; +use ruffle_core::{PlayerBuilder, PlayerRuntime, ViewportDimensions}; use ruffle_render::backend::RenderBackend; use ruffle_render::quality::StageQuality; use serde::Deserialize; @@ -135,6 +135,7 @@ pub struct PlayerOptions { with_renderer: Option, with_audio: bool, with_video: bool, + runtime: PlayerRuntime, } impl PlayerOptions { @@ -157,6 +158,8 @@ impl PlayerOptions { player_builder = player_builder.with_audio(TestAudioBackend::default()); } + player_builder = player_builder.with_player_runtime(self.runtime); + #[cfg(feature = "ruffle_video_software")] if self.with_video { use ruffle_video_software::backend::SoftwareVideoBackend; diff --git a/tests/tests/swfs/avm2/air_datagram_socket/Test.as b/tests/tests/swfs/avm2/air_datagram_socket/Test.as new file mode 100755 index 000000000..462418d40 --- /dev/null +++ b/tests/tests/swfs/avm2/air_datagram_socket/Test.as @@ -0,0 +1,18 @@ +package { + + import flash.display.MovieClip; + + + public class Test extends MovieClip { + + + public function Test() { + } + } + +} + +import flash.net.DatagramSocket; +var socket = new DatagramSocket(); +// TODO - test functionally once we implement it in Ruffle +trace("Socket: " + socket); \ No newline at end of file diff --git a/tests/tests/swfs/avm2/air_datagram_socket/output.txt b/tests/tests/swfs/avm2/air_datagram_socket/output.txt new file mode 100644 index 000000000..8db90f2c8 --- /dev/null +++ b/tests/tests/swfs/avm2/air_datagram_socket/output.txt @@ -0,0 +1 @@ +Socket: [object DatagramSocket] diff --git a/tests/tests/swfs/avm2/air_datagram_socket/test.fla b/tests/tests/swfs/avm2/air_datagram_socket/test.fla new file mode 100755 index 0000000000000000000000000000000000000000..393eece5cfb393cbc9cfeebc4e1804481d8b7ac4 GIT binary patch literal 3884 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 XvD)fq&z-IiVvjf+0GP+F0l@zN7ec_Q literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/air_hidden_lookup/test.swf b/tests/tests/swfs/avm2/air_hidden_lookup/test.swf new file mode 100755 index 0000000000000000000000000000000000000000..2b5a81008e7f7cd8d261e075ab35f9a6b1d1481a GIT binary patch literal 663 zcmV;I0%-k1S5qre0{{ScoPCnra?(H)$Is@2B~ZSkrPNNxjB;a(m{w=}=!I4SwKJAF z;P^G^kcBKIZAfUcP`UL*?6oh^S3n)@BY2BXfSaZ_-ng?lXY>F6a`x7E&Xy&*K3V- z_0>+RRcpyRzE39d3I8k*z0R>|7~{bi@#($37cisM=SxeidyYl@z#zo;T*IeBFEIR3 zujf#M@xsA%49arkH3zN>9 zCal}1eoBw}kI>wn%cxJf%pTK7Dg5JCJ(m&NrGBh6^le6qQE8X5icT0g^2wm(bx$Zu zM#hNQPLMyMOs75DwVCa?FD4B#pfcmTP+V&^51LO^RkZ_^bh|VN?2bd#p6?CRdRRey z&G)AM?GUit{~?It0xC#FQH~W-g+<6>Bw|6v62>TwB?U_fj25t*#zF=QSuEtRa0ADd zFe+elQ;dGLaCHj+AwdzZ1SCjELLkNj31r|@3?$$gAtfPBwcU_KiDddua;8i*{_Mdl z!Bs(&kn#mhHEzTxm{>F8nyF}JLNk+^xuBT>pYLHU8hymM9EEcJE$WN2dXI*>ZLjV5)tD^~-f_ xV}NGG!!)0iL$AviBMa|(DGVm;~MCXR^;=psh_zSk{