diff --git a/core/src/lib.rs b/core/src/lib.rs index 4256f1a24..7127bee24 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -30,7 +30,7 @@ mod library; pub mod loader; mod player; mod prelude; -mod property_map; +pub mod property_map; pub mod shape_utils; pub mod string_utils; pub mod tag_utils; diff --git a/core/src/loader.rs b/core/src/loader.rs index 042eb9cb0..94e3961cb 100644 --- a/core/src/loader.rs +++ b/core/src/loader.rs @@ -6,6 +6,7 @@ use crate::backend::navigator::OwnedFuture; use crate::context::{ActionQueue, ActionType}; use crate::display_object::{DisplayObject, MorphShape, TDisplayObject}; use crate::player::{Player, NEWEST_PLAYER_VERSION}; +use crate::property_map::PropertyMap; use crate::tag_utils::SwfMovie; use crate::vminterface::Instantiator; use crate::xml::XMLNode; @@ -121,6 +122,7 @@ impl<'gc> LoadManager<'gc> { player: Weak>, fetch: OwnedFuture, Error>, url: String, + parameters: PropertyMap, ) -> OwnedFuture<(), Error> { let loader = Loader::RootMovie { self_handle: None }; let handle = self.add_loader(loader); @@ -128,7 +130,7 @@ impl<'gc> LoadManager<'gc> { let loader = self.get_loader_mut(handle).unwrap(); loader.introduce_loader_handle(handle); - loader.root_movie_loader(player, fetch, url) + loader.root_movie_loader(player, fetch, url, parameters) } /// Kick off a movie clip load. @@ -358,6 +360,7 @@ impl<'gc> Loader<'gc> { player: Weak>, fetch: OwnedFuture, Error>, mut url: String, + parameters: PropertyMap, ) -> OwnedFuture<(), Error> { let _handle = match self { Loader::RootMovie { self_handle, .. } => { @@ -383,7 +386,10 @@ impl<'gc> Loader<'gc> { let data = (fetch.await) .and_then(|data| Ok((data.len(), SwfMovie::from_data(&data, Some(url.clone()))?))); - if let Ok((_length, movie)) = data { + if let Ok((_length, mut movie)) = data { + for (key, value) in parameters.iter() { + movie.parameters_mut().insert(key, value.to_owned(), false); + } player.lock().unwrap().set_root_movie(Arc::new(movie)); Ok(()) diff --git a/core/src/player.rs b/core/src/player.rs index 9c3f779b7..8864e1297 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -2,7 +2,7 @@ use crate::avm1::activation::{Activation, ActivationIdentifier}; use crate::avm1::debug::VariableDumper; use crate::avm1::globals::system::SystemProperties; use crate::avm1::object::Object; -use crate::avm1::{Avm1, AvmString, TObject, Timers, Value}; +use crate::avm1::{Avm1, AvmString, ScriptObject, TObject, Timers, Value}; use crate::avm2::Avm2; use crate::backend::input::{InputBackend, MouseCursor}; use crate::backend::locale::LocaleBackend; @@ -19,6 +19,7 @@ use crate::external::{ExternalInterface, ExternalInterfaceProvider}; use crate::library::Library; use crate::loader::LoadManager; use crate::prelude::*; +use crate::property_map::PropertyMap; use crate::tag_utils::SwfMovie; use crate::transform::TransformStack; use crate::vminterface::Instantiator; @@ -312,13 +313,14 @@ impl Player { /// /// This should not be called if a root movie fetch has already been kicked /// off. - pub fn fetch_root_movie(&mut self, movie_url: &str) { + pub fn fetch_root_movie(&mut self, movie_url: &str, parameters: PropertyMap) { self.mutate_with_update_context(|context| { let fetch = context.navigator.fetch(movie_url, RequestOptions::get()); let process = context.load_manager.load_root_movie( context.player.clone().unwrap(), fetch, movie_url.to_string(), + parameters, ); context.navigator.spawn_future(process); @@ -351,7 +353,21 @@ impl Player { MovieClip::from_movie(context.gc_context, context.swf.clone()).into(); root.set_depth(context.gc_context, 0); - root.post_instantiation(context, root, None, Instantiator::Movie, false); + let flashvars = if !context.swf.parameters().is_empty() { + let object = ScriptObject::object(context.gc_context, None); + for (key, value) in context.swf.parameters().iter() { + object.define_value( + context.gc_context, + key, + AvmString::new(context.gc_context, value).into(), + EnumSet::empty(), + ); + } + Some(object.into()) + } else { + None + }; + root.post_instantiation(context, root, flashvars, Instantiator::Movie, false); root.set_name(context.gc_context, ""); context.levels.insert(0, root); diff --git a/core/src/property_map.rs b/core/src/property_map.rs index 57d2eb629..a6dd68ae2 100644 --- a/core/src/property_map.rs +++ b/core/src/property_map.rs @@ -13,7 +13,7 @@ use std::hash::{Hash, Hasher}; type FnvIndexMap = IndexMap; /// A map from property names to values. -#[derive(Debug)] +#[derive(Default, Clone, Debug)] pub struct PropertyMap(FnvIndexMap); impl PropertyMap { @@ -107,6 +107,10 @@ impl PropertyMap { self.0.shift_remove(&CaseInsensitiveStr(key)) } } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } unsafe impl Collect for PropertyMap { diff --git a/core/src/tag_utils.rs b/core/src/tag_utils.rs index 6e0c836c6..06a4784c3 100644 --- a/core/src/tag_utils.rs +++ b/core/src/tag_utils.rs @@ -1,4 +1,5 @@ use crate::backend::navigator::url_from_relative_path; +use crate::property_map::PropertyMap; use gc_arena::Collect; use std::path::Path; use std::sync::Arc; @@ -21,6 +22,9 @@ pub struct SwfMovie { /// The URL the SWF was downloaded from. url: Option, + + /// Any parameters provided when loading this movie (also known as 'flashvars') + parameters: PropertyMap, } impl SwfMovie { @@ -36,6 +40,7 @@ impl SwfMovie { }, data: vec![], url: None, + parameters: PropertyMap::new(), } } @@ -49,6 +54,7 @@ impl SwfMovie { header: self.header.clone(), data, url: source.url.clone(), + parameters: source.parameters.clone(), } } @@ -90,7 +96,12 @@ impl SwfMovie { data }; - Ok(Self { header, data, url }) + Ok(Self { + header, + data, + url, + parameters: PropertyMap::new(), + }) } pub fn header(&self) -> &Header { @@ -118,6 +129,14 @@ impl SwfMovie { pub fn url(&self) -> Option<&str> { self.url.as_deref() } + + pub fn parameters(&self) -> &PropertyMap { + &self.parameters + } + + pub fn parameters_mut(&mut self) -> &mut PropertyMap { + &mut self.parameters + } } /// A shared-ownership reference to some portion of an SWF datastream. diff --git a/web/packages/core/src/ruffle-embed.js b/web/packages/core/src/ruffle-embed.js index 22c887717..cfd3098a5 100644 --- a/web/packages/core/src/ruffle-embed.js +++ b/web/packages/core/src/ruffle-embed.js @@ -17,7 +17,11 @@ module.exports = class RuffleEmbed extends RufflePlayer { connectedCallback() { super.connectedCallback(); - this.stream_swf_url(this.attributes.src.value); + let parameters = null; + if (this.attributes.flashvars) { + parameters = this.attributes.flashvars.value; + } + this.stream_swf_url(this.attributes.src.value, parameters); } get src() { @@ -34,9 +38,12 @@ module.exports = class RuffleEmbed extends RufflePlayer { attributeChangedCallback(name, oldValue, newValue) { super.attributeChangedCallback(name, oldValue, newValue); - console.log(name + " " + oldValue + " " + newValue); if (this.isConnected && name === "src") { - this.stream_swf_url(this.attributes.src.value); + let parameters = null; + if (this.attributes.flashvars) { + parameters = this.attributes.flashvars.value; + } + this.stream_swf_url(this.attributes.src.value, parameters); } } diff --git a/web/packages/core/src/ruffle-object.js b/web/packages/core/src/ruffle-object.js index 91550a97b..cfb2d0ac6 100644 --- a/web/packages/core/src/ruffle-object.js +++ b/web/packages/core/src/ruffle-object.js @@ -32,6 +32,16 @@ module.exports = class RuffleObject extends RufflePlayer { url = this.params.movie; } + let parameters = RuffleObject.find_case_insensitive( + this.params, + "flashvars", + RuffleObject.find_case_insensitive( + this.attributes, + "flashvars", + null + ) + ); + if (url) { this.allow_script_access = allowScriptAccess && @@ -41,7 +51,7 @@ module.exports = class RuffleObject extends RufflePlayer { new URL(url, window.location.href).origin)); //Kick off the SWF download. - this.stream_swf_url(url); + this.stream_swf_url(url, parameters); } } diff --git a/web/packages/core/src/ruffle-player.js b/web/packages/core/src/ruffle-player.js index 517c380bb..c7ba88817 100644 --- a/web/packages/core/src/ruffle-player.js +++ b/web/packages/core/src/ruffle-player.js @@ -10,6 +10,23 @@ exports.FLASH_ACTIVEX_CLASSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"; const DIMENSION_REGEX = /^\s*(\d+(\.\d+)?(%)?)/; +function sanitize_parameters(parameters) { + if (parameters === null || parameters === undefined) { + return {}; + } + if (!(parameters instanceof URLSearchParams)) { + parameters = new URLSearchParams(parameters); + } + const output = {}; + + for (const [key, value] of parameters) { + // Every value must be type of string + output[key] = value.toString(); + } + + return output; +} + exports.RufflePlayer = class RufflePlayer extends HTMLElement { constructor(...args) { let self = super(...args); @@ -152,15 +169,18 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement { * being loaded, or any errors that happen loading it. * * @param {String} url The URL to stream. + * @param {URLSearchParams|String|Object} [parameters] The parameters (also known as "flashvars") to load the movie with. + * If it's a string, it will be decoded into an object. + * If it's an object, every key and value must be a String. */ - async stream_swf_url(url) { + async stream_swf_url(url, parameters) { //TODO: Actually stream files... try { if (this.isConnected && !this.is_unused_fallback_object()) { console.log("Loading SWF file " + url); await this.ensure_fresh_instance(); - this.instance.stream_from(url); + this.instance.stream_from(url, sanitize_parameters(parameters)); if (this.play_button) { this.play_button.style.display = "block"; @@ -205,14 +225,20 @@ exports.RufflePlayer = class RufflePlayer extends HTMLElement { * the movie being loaded. * * @param {String} url The URL to stream. + * @param {URLSearchParams|String|Object} [parameters] The parameters (also known as "flashvars") to load the movie with. + * If it's a string, it will be decoded into an object. + * If it's an object, every key and value must be a String. */ - async play_swf_data(data) { + async play_swf_data(data, parameters) { try { if (this.isConnected && !this.is_unused_fallback_object()) { console.log("Got SWF data"); await this.ensure_fresh_instance(); - this.instance.load_data(new Uint8Array(data)); + this.instance.load_data( + new Uint8Array(data), + sanitize_parameters(parameters) + ); console.log("New Ruffle instance created."); if (this.play_button) { diff --git a/web/src/lib.rs b/web/src/lib.rs index e603acb72..9c63a5e43 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -24,6 +24,7 @@ use ruffle_core::events::MouseWheelDelta; use ruffle_core::external::{ ExternalInterfaceMethod, ExternalInterfaceProvider, Value as ExternalValue, Value, }; +use ruffle_core::property_map::PropertyMap; use ruffle_core::tag_utils::SwfMovie; use ruffle_core::PlayerEvent; use ruffle_web_common::JsResult; @@ -124,22 +125,32 @@ impl Ruffle { /// Stream an arbitrary movie file from (presumably) the Internet. /// /// This method should only be called once per player. - pub fn stream_from(&mut self, movie_url: &str) { + pub fn stream_from(&mut self, movie_url: &str, parameters: &JsValue) -> Result<(), JsValue> { INSTANCES.with(|instances| { let instances = instances.borrow(); let instance = instances.get(self.0).unwrap().borrow(); - instance.core.lock().unwrap().fetch_root_movie(movie_url); - }); + let mut parameters_to_load = PropertyMap::new(); + populate_movie_parameters(¶meters, &mut parameters_to_load)?; + instance + .core + .lock() + .unwrap() + .fetch_root_movie(movie_url, parameters_to_load); + Ok(()) + }) } /// Play an arbitrary movie on this instance. /// /// This method should only be called once per player. - pub fn load_data(&mut self, swf_data: Uint8Array) -> Result<(), JsValue> { + pub fn load_data(&mut self, swf_data: Uint8Array, parameters: &JsValue) -> Result<(), JsValue> { let movie = Arc::new({ let mut data = vec![0; swf_data.length() as usize]; swf_data.copy_to(&mut data[..]); - SwfMovie::from_data(&data, None).map_err(|e| format!("Error loading movie: {}", e))? + let mut movie = SwfMovie::from_data(&data, None) + .map_err(|e| format!("Error loading movie: {}", e))?; + populate_movie_parameters(¶meters, movie.parameters_mut())?; + movie }); INSTANCES.with(|instances| { @@ -899,3 +910,18 @@ pub fn set_panic_handler() { })); }); } + +fn populate_movie_parameters( + input: &JsValue, + output: &mut PropertyMap, +) -> Result<(), JsValue> { + let keys = js_sys::Reflect::own_keys(input)?; + for key in keys.values() { + let key = key?; + let value = js_sys::Reflect::get(input, &key)?; + if let (Some(key), Some(value)) = (key.as_string(), value.as_string()) { + output.insert(&key, value, false); + } + } + Ok(()) +}