avm1: Implement de/serialization of shared objects into Flash Player Lso format

This commit is contained in:
CUB3D 2021-05-04 02:22:01 +01:00 committed by Mike Welsh
parent 3acfb5bc29
commit cd1cde1708
14 changed files with 360 additions and 140 deletions

153
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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,

View File

@ -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<AmfValue> {
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<Element>,
) {
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::<usize>() {
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::<usize>() {
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<Object<'gc>, 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<Value<'gc>, 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(|| "<unknown>".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>(

View File

@ -45,6 +45,7 @@ pub fn xmlnode_constructor<'gc>(
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
println!("Creating xml({:?})", args);
let blank_document = XmlDocument::new(activation.context.gc_context);
match (

View File

@ -2,12 +2,12 @@ use downcast_rs::Downcast;
use std::collections::HashMap;
pub trait StorageBackend: Downcast {
fn get_string(&self, name: &str) -> Option<String>;
fn get(&self, name: &str) -> Option<Vec<u8>>;
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<usize> {
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<String, String>,
map: HashMap<String, Vec<u8>>,
}
impl Default for MemoryStorageBackend {
@ -27,12 +27,12 @@ impl Default for MemoryStorageBackend {
}
impl StorageBackend for MemoryStorageBackend {
fn get_string(&self, name: &str) -> Option<String> {
fn get(&self, name: &str) -> Option<Vec<u8>> {
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
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -25,13 +25,13 @@ impl DiskStorageBackend {
}
impl StorageBackend for DiskStorageBackend {
fn get_string(&self, name: &str) -> Option<String> {
fn get(&self, name: &str) -> Option<Vec<u8>> {
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 {

View File

@ -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>Test</test>
typeof(array.textxml): object
array.date: 2147483647
typeof(array.date): object
o.a: a
o.b: b
delete

View File

@ -1,4 +1,4 @@
class Test {
class test {
static function main(mc) {
var obj = SharedObject.getLocal("RuffleTest", "/");

View File

@ -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"

View File

@ -12,12 +12,18 @@ impl LocalStorageBackend {
}
impl StorageBackend for LocalStorageBackend {
fn get_string(&self, name: &str) -> Option<String> {
self.storage.get(name).unwrap_or_default()
fn get(&self, name: &str) -> Option<Vec<u8>> {
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) {