From cd1cde1708fb288e5df7be6b850d846eff743b33 Mon Sep 17 00:00:00 2001 From: CUB3D Date: Tue, 4 May 2021 02:22:01 +0100 Subject: [PATCH] avm1: Implement de/serialization of shared objects into Flash Player Lso format --- Cargo.lock | 153 ++++++++- core/Cargo.toml | 3 +- core/src/avm1/globals.rs | 4 + core/src/avm1/globals/shared_object.rs | 293 +++++++++++------- core/src/avm1/globals/xml.rs | 1 + core/src/backend/storage.rs | 14 +- .../swfs/avm1/shared_object/RuffleTest.sol | Bin 0 -> 279 bytes core/tests/swfs/avm1/shared_object/test.fla | Bin 0 -> 7066 bytes core/tests/swfs/avm1/shared_object/test.swf | Bin 0 -> 673 bytes desktop/src/storage.rs | 10 +- .../tests/swfs/avm1/shared_object/output2.txt | 5 + tests/tests/swfs/avm1/shared_object/test.as | 2 +- web/Cargo.toml | 1 + web/src/storage.rs | 14 +- 14 files changed, 360 insertions(+), 140 deletions(-) create mode 100644 core/tests/swfs/avm1/shared_object/RuffleTest.sol create mode 100644 core/tests/swfs/avm1/shared_object/test.fla create mode 100644 core/tests/swfs/avm1/shared_object/test.swf diff --git a/Cargo.lock b/Cargo.lock index 6cd32651c..045994507 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,6 +195,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03539c739e37dab2c322ce07b1990089ca1fc7ad14b813e1538bf11bef98fe06" +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block" version = "0.1.6" @@ -517,6 +529,12 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" +[[package]] +name = "cookie-factory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" + [[package]] name = "copyless" version = "0.1.5" @@ -799,8 +817,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c" +dependencies = [ + "darling_core 0.12.4", + "darling_macro 0.12.4", ] [[package]] @@ -817,13 +845,38 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + [[package]] name = "darling_macro" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ - "darling_core", + "darling_core 0.10.2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" +dependencies = [ + "darling_core 0.12.4", "quote", "syn", ] @@ -957,6 +1010,17 @@ dependencies = [ "syn", ] +[[package]] +name = "derive-try-from-primitive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302ccf094df1151173bb6f5a2282fcd2f45accd5eae1bdf82dcbfefbc501ad5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "diff" version = "0.1.12" @@ -1062,6 +1126,28 @@ dependencies = [ "syn", ] +[[package]] +name = "enumset" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbd795df6708a599abf1ee10eacc72efd052b7a5f70fdf0715e4d5151a6db9c3" +dependencies = [ + "enumset_derive", + "serde", +] + +[[package]] +name = "enumset_derive" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19c52f9ec503c8a68dc04daf71a04b07e690c32ab1a8b68e33897f255269d47" +dependencies = [ + "darling 0.12.4", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_logger" version = "0.8.3" @@ -1121,6 +1207,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "flash-lso" +version = "0.5.0" +source = "git+https://github.com/ruffle-rs/rust-flash-lso?rev=e39a8abc897289696672858e30bbc9e43b1c98ac#e39a8abc897289696672858e30bbc9e43b1c98ac" +dependencies = [ + "cookie-factory", + "derive-try-from-primitive", + "enumset", + "nom 6.1.0", + "thiserror", +] + [[package]] name = "flate2" version = "1.0.20" @@ -1180,6 +1278,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "futures" version = "0.3.14" @@ -1795,6 +1899,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 1.0.0", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.89" @@ -2174,7 +2291,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" dependencies = [ - "darling", + "darling 0.10.2", "proc-macro-crate", "proc-macro2", "quote", @@ -2247,6 +2364,8 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab6f70b46d6325aa300f1c7bb3d470127dfc27806d8ea6bf294ee0ce643ce2b1" dependencies = [ + "bitvec", + "lexical-core", "memchr", "version_check", ] @@ -2722,6 +2841,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.8.3" @@ -2882,6 +3007,7 @@ dependencies = [ "downcast-rs", "encoding_rs", "env_logger", + "flash-lso", "flate2", "fnv", "gc-arena", @@ -3024,6 +3150,7 @@ dependencies = [ name = "ruffle_web" version = "0.1.0" dependencies = [ + "base64", "byteorder", "chrono", "console_error_panic_hook", @@ -3275,6 +3402,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "stdweb" version = "0.1.3" @@ -3385,6 +3518,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "termcolor" version = "1.1.2" @@ -4067,6 +4206,12 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "x11-clipboard" version = "0.3.3" diff --git a/core/Cargo.toml b/core/Cargo.toml index 9010edfa2..c8a35224f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -28,7 +28,6 @@ url = "2.2.2" weak-table = "0.3.0" percent-encoding = "2.1.0" thiserror = "1.0" -json = "0.12.4" chrono = "0.4" num-traits = "0.2" instant = "0.1" @@ -37,6 +36,8 @@ rand = { version = "0.8.3", features = ["std", "small_rng"], default-features = serde = { version = "1.0.125", features = ["derive"], optional = true } nellymoser-rs = { git = "https://github.com/ruffle-rs/nellymoser", branch = "main" } regress = "0.2" +flash-lso = { git = "https://github.com/ruffle-rs/rust-flash-lso", rev = "e39a8abc897289696672858e30bbc9e43b1c98ac" } +json = "0.12.4" [dependencies.jpeg-decoder] version = "0.1.22" diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index 827cf8804..285116e71 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -477,6 +477,7 @@ pub struct SystemPrototypes<'gc> { pub array: Object<'gc>, pub array_constructor: Object<'gc>, pub xml_node: Object<'gc>, + pub xml_constructor: Object<'gc>, pub string: Object<'gc>, pub number: Object<'gc>, pub boolean: Object<'gc>, @@ -517,6 +518,7 @@ pub struct SystemPrototypes<'gc> { pub gradient_glow_filter: Object<'gc>, pub gradient_glow_filter_constructor: Object<'gc>, pub date: Object<'gc>, + pub date_constructor: Object<'gc>, pub bitmap_data: Object<'gc>, pub bitmap_data_constructor: Object<'gc>, pub video: Object<'gc>, @@ -1244,6 +1246,7 @@ pub fn create_globals<'gc>( array: array_proto, array_constructor: array, xml_node: xmlnode_proto, + xml_constructor: xml, string: string_proto, number: number_proto, boolean: boolean_proto, @@ -1284,6 +1287,7 @@ pub fn create_globals<'gc>( gradient_glow_filter: gradient_glow_filter_proto, gradient_glow_filter_constructor: gradient_glow_filter, date: date_proto, + date_constructor: date, bitmap_data: bitmap_data_proto, bitmap_data_constructor: bitmap_data, video: video_proto, diff --git a/core/src/avm1/globals/shared_object.rs b/core/src/avm1/globals/shared_object.rs index 3d9c1af0f..8e09c7e23 100644 --- a/core/src/avm1/globals/shared_object.rs +++ b/core/src/avm1/globals/shared_object.rs @@ -1,16 +1,15 @@ use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; +use crate::avm1::object::shared_object::SharedObject; use crate::avm1::property::Attribute; use crate::avm1::{AvmString, Object, TObject, Value}; use crate::avm_warn; use crate::display_object::TDisplayObject; +use flash_lso::types::Value as AmfValue; +use flash_lso::types::{AMFVersion, Element, Lso}; use gc_arena::MutationContext; -use crate::avm1::object::shared_object::SharedObject; - -use json::JsonValue; - pub fn delete_all<'gc>( activation: &mut Activation<'_, 'gc, '_>, _this: Object<'gc>, @@ -29,129 +28,178 @@ pub fn get_disk_usage<'gc>( Ok(Value::Undefined) } +/// Serialize a Value to an AmfValue +fn serialize_value<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + elem: Value<'gc>, +) -> Option { + match elem { + Value::Undefined => Some(AmfValue::Undefined), + Value::Null => Some(AmfValue::Null), + Value::Bool(b) => Some(AmfValue::Bool(b)), + Value::Number(f) => Some(AmfValue::Number(f)), + Value::String(s) => Some(AmfValue::String(s.to_string())), + Value::Object(o) => { + // Don't attempt to serialize functions + let function = activation.context.avm1.prototypes.function; + let array = activation.context.avm1.prototypes.array; + let xml = activation.context.avm1.prototypes.xml_node; + let date = activation.context.avm1.prototypes.date; + + if !o + .is_instance_of(activation, o, function) + .unwrap_or_default() + { + if o.is_instance_of(activation, o, array).unwrap_or_default() { + let mut values = Vec::new(); + let len = o.length(); + recursive_serialize(activation, o, &mut values); + + Some(AmfValue::ECMAArray(vec![], values, len as u32)) + } else if o.is_instance_of(activation, o, xml).unwrap_or_default() { + o.as_xml_node().and_then(|xml_node| { + xml_node + .into_string(&mut |_| true) + .map(|xml_string| AmfValue::XML(xml_string, true)) + .ok() + }) + } else if o.is_instance_of(activation, o, date).unwrap_or_default() { + o.as_date_object() + .and_then(|date_obj| { + date_obj + .date_time() + .map(|date_time| date_time.timestamp_millis()) + }) + .map(|millis| AmfValue::Date(millis as f64, None)) + } else { + let mut object_body = Vec::new(); + recursive_serialize(activation, o, &mut object_body); + Some(AmfValue::Object(object_body, None)) + } + } else { + None + } + } + } +} + /// Serialize an Object and any children to a JSON object -/// It would be best if this was implemented via serde but due to avm and context it can't -/// Undefined fields aren't serialized fn recursive_serialize<'gc>( activation: &mut Activation<'_, 'gc, '_>, obj: Object<'gc>, - json_obj: &mut JsonValue, + elements: &mut Vec, ) { - for k in &obj.get_keys(activation) { - if let Ok(elem) = obj.get(k, activation) { - match elem { - Value::Undefined => {} - Value::Null => json_obj[k] = JsonValue::Null, - Value::Bool(b) => json_obj[k] = b.into(), - Value::Number(f) => json_obj[k] = f.into(), - Value::String(s) => json_obj[k] = s.to_string().into(), - Value::Object(o) => { - // Don't attempt to serialize functions - let function = activation.context.avm1.prototypes.function; - let array = activation.context.avm1.prototypes.array; - if !o - .is_instance_of(activation, o, function) - .unwrap_or_default() - { - let mut sub_data_json = JsonValue::new_object(); - recursive_serialize(activation, o, &mut sub_data_json); - if o.is_instance_of(activation, o, array).unwrap_or_default() { - sub_data_json["__proto__"] = "Array".into(); - sub_data_json["length"] = o.length().into(); - } - json_obj[k] = sub_data_json; + // Reversed to match flash player ordering + for element_name in obj.get_keys(activation).iter().rev() { + if let Ok(elem) = obj.get(element_name, activation) { + if let Some(v) = serialize_value(activation, elem) { + elements.push(Element::new(element_name, v)); + } + } + } +} + +/// Deserialize a AmfValue to a Value +fn deserialize_value<'gc>(activation: &mut Activation<'_, 'gc, '_>, val: &AmfValue) -> Value<'gc> { + match val { + AmfValue::Null => Value::Null, + AmfValue::Undefined => Value::Undefined, + AmfValue::Number(f) => Value::Number(*f), + AmfValue::String(s) => Value::String(AvmString::new(activation.context.gc_context, s)), + AmfValue::Bool(b) => Value::Bool(*b), + AmfValue::ECMAArray(_, associative, len) => { + let array_constructor = activation.context.avm1.prototypes.array_constructor; + if let Ok(Value::Object(obj)) = + array_constructor.construct(activation, &[Value::Number(*len as f64)]) + { + for entry in associative { + let value = deserialize_value(activation, entry.value()); + + if let Ok(i) = entry.name().parse::() { + obj.set_array_element(i, value, activation.context.gc_context); + } else { + obj.define_value( + activation.context.gc_context, + &entry.name, + value, + Attribute::empty(), + ); } } + + obj.into() + } else { + Value::Undefined } } - } -} - -fn recursive_deserialize<'gc>( - json_value: JsonValue, - activation: &mut Activation<'_, 'gc, '_>, -) -> Value<'gc> { - match json_value { - JsonValue::Null => Value::Null, - JsonValue::Short(s) => { - Value::String(AvmString::new(activation.context.gc_context, s.to_string())) - } - JsonValue::String(s) => Value::String(AvmString::new(activation.context.gc_context, s)), - JsonValue::Number(f) => Value::Number(f.into()), - JsonValue::Boolean(b) => Value::Bool(b), - JsonValue::Object(o) => { - if o.get("__proto__").and_then(JsonValue::as_str) == Some("Array") { - deserialize_array(o, activation) + AmfValue::Object(elements, _) => { + // Deserialize Object + let obj_proto = activation.context.avm1.prototypes.object; + if let Ok(obj) = obj_proto.create_bare_object(activation, obj_proto) { + for entry in elements { + let value = deserialize_value(activation, entry.value()); + obj.define_value( + activation.context.gc_context, + &entry.name, + value, + Attribute::empty(), + ); + } + obj.into() } else { - deserialize_object(o, activation) + Value::Undefined } } - JsonValue::Array(_) => Value::Undefined, - } -} + AmfValue::Date(time, _) => { + let date_proto = activation.context.avm1.prototypes.date_constructor; -/// Deserialize an Object and any children from a JSON object -/// It would be best if this was implemented via serde but due to avm and context it can't -/// Undefined fields aren't deserialized -fn deserialize_object<'gc>( - json_obj: json::object::Object, - activation: &mut Activation<'_, 'gc, '_>, -) -> Value<'gc> { - // Deserialize Object - let obj_proto = activation.context.avm1.prototypes.object; - if let Ok(obj) = obj_proto.create_bare_object(activation, obj_proto) { - for entry in json_obj.iter() { - let value = recursive_deserialize(entry.1.clone(), activation); - obj.define_value( - activation.context.gc_context, - entry.0, - value, - Attribute::empty(), - ); - } - obj.into() - } else { - Value::Undefined - } -} - -/// Deserialize an Object and any children from a JSON object -/// It would be best if this was implemented via serde but due to avm and context it can't -/// Undefined fields aren't deserialized -fn deserialize_array<'gc>( - mut json_obj: json::object::Object, - activation: &mut Activation<'_, 'gc, '_>, -) -> Value<'gc> { - let array_constructor = activation.context.avm1.prototypes.array_constructor; - let len = json_obj - .get("length") - .and_then(JsonValue::as_i32) - .unwrap_or_default(); - if let Ok(Value::Object(obj)) = array_constructor.construct(activation, &[len.into()]) { - // Remove length and proto meta-properties. - json_obj.remove("length"); - json_obj.remove("__proto__"); - - for entry in json_obj.iter() { - let value = recursive_deserialize(entry.1.clone(), activation); - if let Ok(i) = entry.0.parse::() { - obj.set_array_element(i, value, activation.context.gc_context); + if let Ok(Value::Object(obj)) = + date_proto.construct(activation, &[Value::Number(*time)]) + { + Value::Object(obj) } else { - obj.define_value( + Value::Undefined + } + } + AmfValue::XML(content, _) => { + let xml_proto = activation.context.avm1.prototypes.xml_constructor; + + if let Ok(Value::Object(obj)) = xml_proto.construct( + activation, + &[Value::String(AvmString::new( activation.context.gc_context, - entry.0, - value, - Attribute::empty(), - ); + content, + ))], + ) { + Value::Object(obj) + } else { + Value::Undefined } } - obj.into() - } else { - Value::Undefined + _ => Value::Undefined, } } +fn deserialize_lso<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + lso: &Lso, +) -> Result, Error<'gc>> { + let obj_proto = activation.context.avm1.prototypes.object; + let obj = obj_proto.create_bare_object(activation, obj_proto)?; + + for child in &lso.body { + obj.define_value( + activation.context.gc_context, + &child.name, + deserialize_value(activation, child.value()), + Attribute::empty(), + ); + } + + Ok(obj) +} + pub fn get_local<'gc>( activation: &mut Activation<'_, 'gc, '_>, _this: Object<'gc>, @@ -263,18 +311,18 @@ pub fn get_local<'gc>( let obj_so = this.as_shared_object().unwrap(); obj_so.set_name(activation.context.gc_context, full_name.clone()); - let prototype = activation.context.avm1.prototypes.object; let mut data = Value::Undefined; // Load the data object from storage if it existed prior - if let Some(saved) = activation.context.storage.get_string(&full_name) { - if let Ok(json_data) = json::parse(&saved) { - data = recursive_deserialize(json_data, activation); + if let Some(saved) = activation.context.storage.get(&full_name) { + if let Ok(lso) = flash_lso::read::Reader::default().parse(&saved) { + data = deserialize_lso(activation, &lso)?.into(); } } if data == Value::Undefined { // No data; create a fresh data object. + let prototype = activation.context.avm1.prototypes.object; data = prototype.create_bare_object(activation, prototype)?.into(); } @@ -443,17 +491,26 @@ pub fn flush<'gc>( ) -> Result, Error<'gc>> { let data = this.get("data", activation)?.coerce_to_object(activation); - let mut data_json = JsonValue::new_object(); - recursive_serialize(activation, data, &mut data_json); - let this_obj = this.as_shared_object().unwrap(); let name = this_obj.get_name(); - Ok(activation - .context - .storage - .put_string(&name, data_json.dump()) - .into()) + let mut elements = Vec::new(); + recursive_serialize(activation, data, &mut elements); + let mut lso = Lso::new( + elements, + &name + .split('/') + .last() + .map(|e| e.to_string()) + .unwrap_or_else(|| "".to_string()), + AMFVersion::AMF0, + ); + + // TODO: make write_to_bytes return result + + let bytes = flash_lso::write::write_to_bytes(&mut lso).unwrap_or_default(); + + Ok(activation.context.storage.put(&name, &bytes).into()) } pub fn get_size<'gc>( diff --git a/core/src/avm1/globals/xml.rs b/core/src/avm1/globals/xml.rs index 0c2f48ab4..cf362c959 100644 --- a/core/src/avm1/globals/xml.rs +++ b/core/src/avm1/globals/xml.rs @@ -45,6 +45,7 @@ pub fn xmlnode_constructor<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { + println!("Creating xml({:?})", args); let blank_document = XmlDocument::new(activation.context.gc_context); match ( diff --git a/core/src/backend/storage.rs b/core/src/backend/storage.rs index 356eab6a8..498874c85 100644 --- a/core/src/backend/storage.rs +++ b/core/src/backend/storage.rs @@ -2,12 +2,12 @@ use downcast_rs::Downcast; use std::collections::HashMap; pub trait StorageBackend: Downcast { - fn get_string(&self, name: &str) -> Option; + fn get(&self, name: &str) -> Option>; - fn put_string(&mut self, name: &str, value: String) -> bool; + fn put(&mut self, name: &str, value: &[u8]) -> bool; fn get_size(&self, name: &str) -> Option { - self.get_string(name).map(|x| x.as_bytes().len()) + self.get(name).map(|x| x.len()) } fn remove_key(&mut self, name: &str); @@ -15,7 +15,7 @@ pub trait StorageBackend: Downcast { impl_downcast!(StorageBackend); pub struct MemoryStorageBackend { - map: HashMap, + map: HashMap>, } impl Default for MemoryStorageBackend { @@ -27,12 +27,12 @@ impl Default for MemoryStorageBackend { } impl StorageBackend for MemoryStorageBackend { - fn get_string(&self, name: &str) -> Option { + fn get(&self, name: &str) -> Option> { self.map.get(name).cloned() } - fn put_string(&mut self, name: &str, value: String) -> bool { - self.map.insert(name.into(), value); + fn put(&mut self, name: &str, value: &[u8]) -> bool { + self.map.insert(name.into(), value.to_vec()); true } diff --git a/core/tests/swfs/avm1/shared_object/RuffleTest.sol b/core/tests/swfs/avm1/shared_object/RuffleTest.sol new file mode 100644 index 0000000000000000000000000000000000000000..55f95537414b4b8ef368696c8a821a1e5725f0e7 GIT binary patch literal 279 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 H+>l<%UfMqj literal 0 HcmV?d00001 diff --git a/desktop/src/storage.rs b/desktop/src/storage.rs index 968098905..50b23effc 100644 --- a/desktop/src/storage.rs +++ b/desktop/src/storage.rs @@ -25,13 +25,13 @@ impl DiskStorageBackend { } impl StorageBackend for DiskStorageBackend { - fn get_string(&self, name: &str) -> Option { + fn get(&self, name: &str) -> Option> { let full_path = self.base_path.join(Path::new(name)); match File::open(full_path) { Ok(mut file) => { - let mut buffer = String::new(); - if let Err(r) = file.read_to_string(&mut buffer) { + let mut buffer = Vec::new(); + if let Err(r) = file.read_to_end(&mut buffer) { log::warn!("Unable to read file content {:?}", r); None } else { @@ -45,7 +45,7 @@ impl StorageBackend for DiskStorageBackend { } } - fn put_string(&mut self, name: &str, value: String) -> bool { + fn put(&mut self, name: &str, value: &[u8]) -> bool { let full_path = self.base_path.join(Path::new(name)); if let Some(parent_dir) = full_path.parent() { if !parent_dir.exists() { @@ -58,7 +58,7 @@ impl StorageBackend for DiskStorageBackend { match File::create(full_path) { Ok(mut file) => { - if let Err(r) = file.write_all(value.as_bytes()) { + if let Err(r) = file.write_all(&value) { log::warn!("Unable to write file content {:?}", r); false } else { diff --git a/tests/tests/swfs/avm1/shared_object/output2.txt b/tests/tests/swfs/avm1/shared_object/output2.txt index ca89f6458..a8adfad23 100644 --- a/tests/tests/swfs/avm1/shared_object/output2.txt +++ b/tests/tests/swfs/avm1/shared_object/output2.txt @@ -7,6 +7,11 @@ array.hasOwnProperty('0'): true array.hasOwnProperty('1'): false array['prop']: property array[-1]: elem negative one +array.denseArray: 1,2,3 +array.textxml: Test +typeof(array.textxml): object +array.date: 2147483647 +typeof(array.date): object o.a: a o.b: b delete diff --git a/tests/tests/swfs/avm1/shared_object/test.as b/tests/tests/swfs/avm1/shared_object/test.as index e8a6777cf..9651ca62d 100644 --- a/tests/tests/swfs/avm1/shared_object/test.as +++ b/tests/tests/swfs/avm1/shared_object/test.as @@ -1,4 +1,4 @@ -class Test { +class test { static function main(mc) { var obj = SharedObject.getLocal("RuffleTest", "/"); diff --git a/web/Cargo.toml b/web/Cargo.toml index 821312702..214b72e7d 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -38,6 +38,7 @@ chrono = { version = "0.4", features = ["wasmbind"] } getrandom = { version = "0.2", features = ["js"] } serde = { version = "1.0.125", features = ["derive"] } thiserror = "1.0" +base64 = "0.13.0" [dependencies.ruffle_core] path = "../core" diff --git a/web/src/storage.rs b/web/src/storage.rs index d7b0edac5..297a128f3 100644 --- a/web/src/storage.rs +++ b/web/src/storage.rs @@ -12,12 +12,18 @@ impl LocalStorageBackend { } impl StorageBackend for LocalStorageBackend { - fn get_string(&self, name: &str) -> Option { - self.storage.get(name).unwrap_or_default() + fn get(&self, name: &str) -> Option> { + if let Ok(Some(data)) = self.storage.get(name) { + if let Ok(data) = base64::decode(&data) { + return Some(data); + } + } + + None } - fn put_string(&mut self, name: &str, value: String) -> bool { - self.storage.set(name, &value).is_ok() + fn put(&mut self, name: &str, value: &[u8]) -> bool { + self.storage.set(name, &base64::encode(value)).is_ok() } fn remove_key(&mut self, name: &str) {