From 1ee61cca57b3a4275faebc6b15c27398ef8eb93b Mon Sep 17 00:00:00 2001 From: EmperorBale <37880918+EmperorBale@users.noreply.github.com> Date: Tue, 22 Jun 2021 01:26:27 -0700 Subject: [PATCH] avm2: ByteArray updates --- Cargo.lock | 1 + core/Cargo.toml | 3 +- core/src/avm2/activation.rs | 97 ++-- core/src/avm2/bytearray.rs | 485 +++++++++--------- core/src/avm2/globals.rs | 8 + .../avm2/globals/flash/display/loaderinfo.rs | 7 +- core/src/avm2/globals/flash/utils.rs | 1 + .../src/avm2/globals/flash/utils/bytearray.rs | 138 +++-- .../flash/utils/compression_algorithm.rs | 46 ++ tests/tests/swfs/avm2/bytearray/Test.as | 117 +++-- tests/tests/swfs/avm2/bytearray/output.txt | 54 +- tests/tests/swfs/avm2/bytearray/test.swf | Bin 1446 -> 1679 bytes 12 files changed, 518 insertions(+), 439 deletions(-) create mode 100644 core/src/avm2/globals/flash/utils/compression_algorithm.rs diff --git a/Cargo.lock b/Cargo.lock index 57badc150..506e75379 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3143,6 +3143,7 @@ dependencies = [ "jpeg-decoder", "json", "log", + "lzma-rs", "minimp3", "nellymoser-rs", "num-derive", diff --git a/core/Cargo.toml b/core/Cargo.toml index 533471514..109ebbb53 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -38,6 +38,7 @@ nellymoser-rs = { git = "https://github.com/ruffle-rs/nellymoser", branch = "mai regress = "0.3" flash-lso = { git = "https://github.com/ruffle-rs/rust-flash-lso", rev = "e39a8abc897289696672858e30bbc9e43b1c98ac" } json = "0.12.4" +lzma-rs = {version = "0.2.0", optional = true } [dependencies.jpeg-decoder] version = "0.1.22" @@ -50,6 +51,6 @@ env_logger = "0.8.4" [features] default = ["minimp3", "serde"] -lzma = ["swf/lzma"] +lzma = ["lzma-rs", "swf/lzma"] wasm-bindgen = [ "instant/wasm-bindgen" ] avm_debug = [] diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 553e1d370..aed978aca 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -18,7 +18,7 @@ use crate::context::UpdateContext; use crate::swf::extensions::ReadSwfExt; use gc_arena::{Gc, GcCell, MutationContext}; use smallvec::SmallVec; -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; use swf::avm2::read::Reader; use swf::avm2::types::{ Class as AbcClass, Index, Method as AbcMethod, Multiname as AbcMultiname, @@ -2568,11 +2568,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let mut dm = dm .as_bytearray_mut(self.context.gc_context) .ok_or_else(|| "Unable to get bytearray storage".to_string())?; - if address < 0 || address as usize >= dm.len() { - return Err("RangeError: The specified range is invalid".into()); - } - dm.write_bytes_at(&val.to_le_bytes(), address as usize); + let address = + usize::try_from(address).map_err(|_| "RangeError: The specified range is invalid")?; + dm.write_at_nongrowing(&val.to_le_bytes(), address)?; Ok(FrameControl::Continue) } @@ -2587,11 +2586,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { .as_bytearray_mut(self.context.gc_context) .ok_or_else(|| "Unable to get bytearray storage".to_string())?; - if address < 0 || (address as usize + 1) >= dm.len() { - return Err("RangeError: The specified range is invalid".into()); - } - - dm.write_bytes_at(&val.to_le_bytes(), address as usize); + let address = + usize::try_from(address).map_err(|_| "RangeError: The specified range is invalid")?; + dm.write_at_nongrowing(&val.to_le_bytes(), address)?; Ok(FrameControl::Continue) } @@ -2606,11 +2603,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { .as_bytearray_mut(self.context.gc_context) .ok_or_else(|| "Unable to get bytearray storage".to_string())?; - if address < 0 || (address as usize + 3) >= dm.len() { - return Err("RangeError: The specified range is invalid".into()); - } - - dm.write_bytes_at(&val.to_le_bytes(), address as usize); + let address = + usize::try_from(address).map_err(|_| "RangeError: The specified range is invalid")?; + dm.write_at_nongrowing(&val.to_le_bytes(), address)?; Ok(FrameControl::Continue) } @@ -2625,11 +2620,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { .as_bytearray_mut(self.context.gc_context) .ok_or_else(|| "Unable to get bytearray storage".to_string())?; - if address < 0 || (address as usize + 3) >= dm.len() { - return Err("RangeError: The specified range is invalid".into()); - } - - dm.write_bytes_at(&val.to_le_bytes(), address as usize); + let address = + usize::try_from(address).map_err(|_| "RangeError: The specified range is invalid")?; + dm.write_at_nongrowing(&val.to_le_bytes(), address)?; Ok(FrameControl::Continue) } @@ -2644,11 +2637,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { .as_bytearray_mut(self.context.gc_context) .ok_or_else(|| "Unable to get bytearray storage".to_string())?; - if address < 0 || (address as usize + 7) >= dm.len() { - return Err("RangeError: The specified range is invalid".into()); - } - - dm.write_bytes_at(&val.to_le_bytes(), address as usize); + let address = + usize::try_from(address).map_err(|_| "RangeError: The specified range is invalid")?; + dm.write_at_nongrowing(&val.to_le_bytes(), address)?; Ok(FrameControl::Continue) } @@ -2664,7 +2655,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let val = dm.get(address); if let Some(val) = val { - self.context.avm2.push(Value::Integer(val as i32)); + self.context.avm2.push(val); } else { return Err("RangeError: The specified range is invalid".into()); } @@ -2680,15 +2671,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let dm = dm .as_bytearray() .ok_or_else(|| "Unable to get bytearray storage".to_string())?; - let val = dm.get_range(address..address + 2); - - if let Some(val) = val { - self.context.avm2.push(Value::Integer( - u16::from_le_bytes(val.try_into().unwrap()) as i32 - )); - } else { - return Err("RangeError: The specified range is invalid".into()); - } + let val = dm.read_at(2, address)?; + self.context + .avm2 + .push(u16::from_le_bytes(val.try_into().unwrap())); Ok(FrameControl::Continue) } @@ -2701,16 +2687,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let dm = dm .as_bytearray() .ok_or_else(|| "Unable to get bytearray storage".to_string())?; - let val = dm.get_range(address..address + 4); - - if let Some(val) = val { - self.context - .avm2 - .push(Value::Integer(i32::from_le_bytes(val.try_into().unwrap()))); - } else { - return Err("RangeError: The specified range is invalid".into()); - } - + let val = dm.read_at(4, address)?; + self.context + .avm2 + .push(i32::from_le_bytes(val.try_into().unwrap())); Ok(FrameControl::Continue) } @@ -2722,15 +2702,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let dm = dm .as_bytearray() .ok_or_else(|| "Unable to get bytearray storage".to_string())?; - let val = dm.get_range(address..address + 4); - - if let Some(val) = val { - self.context.avm2.push(Value::Number( - f32::from_le_bytes(val.try_into().unwrap()) as f64 - )); - } else { - return Err("RangeError: The specified range is invalid".into()); - } + let val = dm.read_at(4, address)?; + self.context + .avm2 + .push(f32::from_le_bytes(val.try_into().unwrap())); Ok(FrameControl::Continue) } @@ -2743,16 +2718,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { let dm = dm .as_bytearray() .ok_or_else(|| "Unable to get bytearray storage".to_string())?; - let val = dm.get_range(address..address + 8); - - if let Some(val) = val { - self.context - .avm2 - .push(Value::Number(f64::from_le_bytes(val.try_into().unwrap()))); - } else { - return Err("RangeError: The specified range is invalid".into()); - } - + let val = dm.read_at(8, address)?; + self.context + .avm2 + .push(f64::from_le_bytes(val.try_into().unwrap())); Ok(FrameControl::Continue) } diff --git a/core/src/avm2/bytearray.rs b/core/src/avm2/bytearray.rs index 354579aa9..94a811dbd 100644 --- a/core/src/avm2/bytearray.rs +++ b/core/src/avm2/bytearray.rs @@ -2,11 +2,13 @@ use crate::avm2::Error; use flate2::read::*; use flate2::Compression; use gc_arena::Collect; +use std::cell::Cell; use std::cmp; use std::convert::{TryFrom, TryInto}; -use std::io; +use std::fmt::{self, Display, Formatter}; use std::io::prelude::*; -use std::ops::Range; +use std::io::{self, Read, SeekFrom}; +use std::str::FromStr; #[derive(Clone, Collect, Debug)] #[collect(no_drop)] @@ -15,16 +17,47 @@ pub enum Endian { Little, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CompressionAlgorithm { + Zlib, + Deflate, + Lzma, +} + +impl Display for CompressionAlgorithm { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let s = match *self { + CompressionAlgorithm::Zlib => "zlib", + CompressionAlgorithm::Deflate => "deflate", + CompressionAlgorithm::Lzma => "lzma", + }; + f.write_str(s) + } +} + +impl FromStr for CompressionAlgorithm { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "zlib" => CompressionAlgorithm::Zlib, + "deflate" => CompressionAlgorithm::Deflate, + "lzma" => CompressionAlgorithm::Lzma, + _ => return Err("Unknown compression algorithm".into()), + }) + } +} + #[derive(Clone, Collect, Debug)] #[collect(no_drop)] pub struct ByteArrayStorage { /// Underlying ByteArray bytes: Vec, - // The current position to read/write from - position: usize, + /// The current position to read/write from + position: Cell, - /// This represents what endian to use while reading data. + /// This represents what endian to use while reading/writing data. endian: Endian, } @@ -33,272 +66,156 @@ impl ByteArrayStorage { pub fn new() -> ByteArrayStorage { ByteArrayStorage { bytes: Vec::new(), - position: 0, + position: Cell::new(0), endian: Endian::Big, } } - /// Write a byte at next position in the bytearray - pub fn write_byte(&mut self, byte: u8) { - let bytes_len = self.bytes.len(); - // Allocate space for the byte - self.position += 1; - if self.position > bytes_len { - self.bytes.resize(self.position, 0); - } - self.bytes[self.position - 1] = byte; + /// Write bytes at the next position in the ByteArray, growing if needed. + #[inline] + pub fn write_bytes(&mut self, buf: &[u8]) -> Result<(), Error> { + self.write_at(buf, self.position.get())?; + self.position.set(self.position.get() + buf.len()); + Ok(()) } - /// Write bytes at next position in bytearray (This function is similar to whats in std::io::Cursor) - pub fn write_bytes(&mut self, buf: &[u8]) { - // Make sure the internal buffer is as least as big as where we - // currently are - let len = self.bytes.len(); - if len < self.position { - // use `resize` so that the zero filling is as efficient as possible - self.bytes.resize(self.position, 0); - } - // Figure out what bytes will be used to overwrite what's currently - // there (left), and what will be appended on the end (right) - { - let space = self.bytes.len() - self.position; - let (left, right) = buf.split_at(cmp::min(space, buf.len())); - self.bytes[self.position..self.position + left.len()].copy_from_slice(left); - self.bytes.extend_from_slice(right); - } - - // Bump us forward - self.position += buf.len(); + /// Reads any amount of bytes from the current position in the ByteArray + #[inline] + pub fn read_bytes(&self, amnt: usize) -> Result<&[u8], Error> { + let bytes = self.read_at(amnt, self.position.get())?; + self.position.set(self.position.get() + amnt); + Ok(bytes) } - // Write bytes at an offset, ignoring the current position - pub fn write_bytes_at(&mut self, buf: &[u8], offset: usize) { - // Make sure the internal buffer is as least as big as where we - // currently are - let len = self.bytes.len(); - if len < offset { - // use `resize` so that the zero filling is as efficient as possible - self.bytes.resize(offset, 0); - } - // Figure out what bytes will be used to overwrite what's currently - // there (left), and what will be appended on the end (right) - { - let space = self.bytes.len() - offset; - let (left, right) = buf.split_at(cmp::min(space, buf.len())); - self.bytes[offset..offset + left.len()].copy_from_slice(left); - self.bytes.extend_from_slice(right); - } + /// Reads any amount of bytes at any offset in the ByteArray + #[inline] + pub fn read_at(&self, amnt: usize, offset: usize) -> Result<&[u8], Error> { + self.bytes + .get(offset..) + .and_then(|bytes| bytes.get(..amnt)) + .ok_or_else(|| "EOFError: Reached EOF".into()) } - pub fn clear(&mut self) { - self.bytes.clear(); - // According to docs, this is where the bytearray should free resources - self.bytes.shrink_to_fit(); - self.position = 0; + /// Write bytes at any offset in the ByteArray + /// Will automatically grow the ByteArray to fit the new buffer + pub fn write_at(&mut self, buf: &[u8], offset: usize) -> Result<(), Error> { + let new_len = offset + .checked_add(buf.len()) + .ok_or("RangeError: Cannot overflow usize")?; + if self.len() < new_len { + self.set_length(new_len); + } + // SAFETY: + // The storage is garunteed to be at least the size of new_len because we just resized it. + unsafe { + self.bytes + .get_unchecked_mut(offset..new_len) + .copy_from_slice(buf) + } + Ok(()) } - // Returns the bytearray compressed with zlib - pub fn zlib_compress(&mut self) -> io::Result> { + /// Write bytes at any offset in the ByteArray + /// Will return an error if the new buffer does not fit the ByteArray + pub fn write_at_nongrowing(&mut self, buf: &[u8], offset: usize) -> Result<(), Error> { + self.bytes + .get_mut(offset..) + .and_then(|bytes| bytes.get_mut(..buf.len())) + .ok_or("RangeError: The specified range is invalid")? + .copy_from_slice(buf); + Ok(()) + } + + /// Compress the ByteArray into a temporary buffer + pub fn compress(&mut self, algorithm: CompressionAlgorithm) -> Result, Error> { let mut buffer = Vec::new(); - let mut compresser = ZlibEncoder::new(&*self.bytes, Compression::fast()); - compresser.read_to_end(&mut buffer)?; + match algorithm { + CompressionAlgorithm::Zlib => { + let mut compresser = ZlibEncoder::new(&*self.bytes, Compression::fast()); + compresser.read_to_end(&mut buffer)?; + } + CompressionAlgorithm::Deflate => { + let mut compresser = DeflateEncoder::new(&*self.bytes, Compression::fast()); + compresser.read_to_end(&mut buffer)?; + } + #[cfg(feature = "lzma")] + CompressionAlgorithm::Lzma => lzma_rs::lzma_compress(&mut &*self.bytes, &mut buffer)?, + #[cfg(not(feature = "lzma"))] + CompressionAlgorithm::Lzma => { + return Err("Ruffle was not compiled with LZMA support".into()) + } + } Ok(buffer) } - // Returns the bytearray compressed with deflate - pub fn deflate_compress(&mut self) -> io::Result> { + /// Decompress the ByteArray into a temporary buffer + pub fn decompress(&mut self, algorithm: CompressionAlgorithm) -> Result, Error> { let mut buffer = Vec::new(); - let mut compresser = DeflateEncoder::new(&*self.bytes, Compression::fast()); - compresser.read_to_end(&mut buffer)?; - Ok(buffer) - } - - // Returns the bytearray decompressed with zlib - pub fn zlib_decompress(&mut self) -> io::Result> { - let mut buffer = Vec::new(); - let mut compresser = ZlibDecoder::new(&*self.bytes); - compresser.read_to_end(&mut buffer)?; - Ok(buffer) - } - - // Returns the bytearray decompressed with deflate - pub fn deflate_decompress(&mut self) -> io::Result> { - let mut buffer = Vec::new(); - let mut compresser = DeflateDecoder::new(&*self.bytes); - compresser.read_to_end(&mut buffer)?; - Ok(buffer) - } - - /// Set a new length for the bytearray - pub fn set_length(&mut self, new_len: usize) { - self.bytes.resize(new_len, 0); - } - - // Reads exactly an amount of data - pub fn read_exact(&mut self, amnt: usize) -> Result<&[u8], Error> { - if self.position + amnt > self.bytes.len() { - return Err("EOFError: Reached EOF".into()); + match algorithm { + CompressionAlgorithm::Zlib => { + let mut compresser = ZlibDecoder::new(&*self.bytes); + compresser.read_to_end(&mut buffer)?; + } + CompressionAlgorithm::Deflate => { + let mut compresser = DeflateDecoder::new(&*self.bytes); + compresser.read_to_end(&mut buffer)?; + } + #[cfg(feature = "lzma")] + CompressionAlgorithm::Lzma => lzma_rs::lzma_decompress(&mut &*self.bytes, &mut buffer)?, + #[cfg(not(feature = "lzma"))] + CompressionAlgorithm::Lzma => { + return Err("Ruffle was not compiled with LZMA support".into()) + } } - let val = Ok(&self.bytes[self.position..self.position + amnt]); - self.position += amnt; - val + Ok(buffer) } - pub fn read_utf(&mut self) -> Result { + pub fn read_utf(&self) -> Result { let len = self.read_unsigned_short()?; - let val = String::from_utf8_lossy(self.read_exact(len as usize)?); + let val = String::from_utf8_lossy(self.read_bytes(len.into())?); Ok(val.into_owned()) } - // Reads a i16 from the buffer - pub fn read_short(&mut self) -> Result { - Ok(match self.endian { - Endian::Big => i16::from_be_bytes(self.read_exact(2)?.try_into().unwrap()), - Endian::Little => i16::from_le_bytes(self.read_exact(2)?.try_into().unwrap()), - }) + pub fn write_boolean(&mut self, val: bool) -> Result<(), Error> { + self.write_bytes(&[val as u8; 1]) } - // Reads a u16 from the buffer - pub fn read_unsigned_short(&mut self) -> Result { - Ok(match self.endian { - Endian::Big => u16::from_be_bytes(self.read_exact(2)?.try_into().unwrap()), - Endian::Little => u16::from_le_bytes(self.read_exact(2)?.try_into().unwrap()), - }) - } - - // Reads a f64 from the buffer - pub fn read_double(&mut self) -> Result { - Ok(match self.endian { - Endian::Big => f64::from_be_bytes(self.read_exact(8)?.try_into().unwrap()), - Endian::Little => f64::from_le_bytes(self.read_exact(8)?.try_into().unwrap()), - }) - } - - // Reads a f32 from the buffer - pub fn read_float(&mut self) -> Result { - Ok(match self.endian { - Endian::Big => f32::from_be_bytes(self.read_exact(4)?.try_into().unwrap()), - Endian::Little => f32::from_le_bytes(self.read_exact(4)?.try_into().unwrap()), - }) - } - - // Reads a i32 from the buffer - pub fn read_int(&mut self) -> Result { - Ok(match self.endian { - Endian::Big => i32::from_be_bytes(self.read_exact(4)?.try_into().unwrap()), - Endian::Little => i32::from_le_bytes(self.read_exact(4)?.try_into().unwrap()), - }) - } - - // Reads a u32 from the buffer - pub fn read_unsigned_int(&mut self) -> Result { - Ok(match self.endian { - Endian::Big => u32::from_be_bytes(self.read_exact(4)?.try_into().unwrap()), - Endian::Little => u32::from_le_bytes(self.read_exact(4)?.try_into().unwrap()), - }) - } - - // Reads byte from buffer, returns false if zero, otherwise true - pub fn read_boolean(&mut self) -> Result { - Ok(*self.read_exact(1)?.first().unwrap() != 0) - } - - // Reads a i8 from the buffer - pub fn read_byte(&mut self) -> Result { - Ok(match self.endian { - Endian::Big => i8::from_be_bytes(self.read_exact(1)?.try_into().unwrap()), - Endian::Little => i8::from_le_bytes(self.read_exact(1)?.try_into().unwrap()), - }) - } - - // Reads a u8 from the buffer - pub fn read_unsigned_byte(&mut self) -> Result { - Ok(match self.endian { - Endian::Big => u8::from_be_bytes(self.read_exact(1)?.try_into().unwrap()), - Endian::Little => u8::from_le_bytes(self.read_exact(1)?.try_into().unwrap()), - }) - } - - // Writes a f32 to the buffer - pub fn write_float(&mut self, val: f32) { - let float_bytes = match self.endian { - Endian::Big => val.to_be_bytes(), - Endian::Little => val.to_le_bytes(), - }; - self.write_bytes(&float_bytes); - } - - // Writes a f64 to the buffer - pub fn write_double(&mut self, val: f64) { - let double_bytes = match self.endian { - Endian::Big => val.to_be_bytes(), - Endian::Little => val.to_le_bytes(), - }; - self.write_bytes(&double_bytes); - } - - // Writes a 1 byte to the buffer, either 1 or 0 - pub fn write_boolean(&mut self, val: bool) { - self.write_bytes(&[val as u8; 1]); - } - - // Writes a i32 to the buffer - pub fn write_int(&mut self, val: i32) { - let int_bytes = match self.endian { - Endian::Big => val.to_be_bytes(), - Endian::Little => val.to_le_bytes(), - }; - self.write_bytes(&int_bytes); - } - - // Writes a u32 to the buffer - pub fn write_unsigned_int(&mut self, val: u32) { - let uint_bytes = match self.endian { - Endian::Big => val.to_be_bytes(), - Endian::Little => val.to_le_bytes(), - }; - self.write_bytes(&uint_bytes); - } - - // Writes a i16 to the buffer - pub fn write_short(&mut self, val: i16) { - let short_bytes = match self.endian { - Endian::Big => val.to_be_bytes(), - Endian::Little => val.to_le_bytes(), - }; - self.write_bytes(&short_bytes); - } - - // Writes a u16 to the buffer - pub fn write_unsigned_short(&mut self, val: u16) { - let ushort_bytes = match self.endian { - Endian::Big => val.to_be_bytes(), - Endian::Little => val.to_le_bytes(), - }; - self.write_bytes(&ushort_bytes); + pub fn read_boolean(&self) -> Result { + Ok(self.read_bytes(1)? != [0]) } // Writes a UTF String into the buffer, with its length as a prefix pub fn write_utf(&mut self, utf_string: &str) -> Result<(), Error> { if let Ok(str_size) = u16::try_from(utf_string.len()) { - self.write_unsigned_short(str_size); - self.write_bytes(utf_string.as_bytes()); + self.write_unsigned_short(str_size)?; + self.write_bytes(utf_string.as_bytes()) } else { - return Err("RangeError: UTF String length must fit into a short".into()); + Err("RangeError: UTF String length must fit into a short".into()) } - Ok(()) } - pub fn get(&self, item: usize) -> Option { - self.bytes.get(item).copied() + #[inline] + pub fn clear(&mut self) { + self.bytes.clear(); + self.position.set(0) } - pub fn get_range(&self, item: Range) -> Option<&[u8]> { - self.bytes.get(item) + #[inline] + pub fn shrink_to_fit(&mut self) { + self.bytes.shrink_to_fit() + } + + #[inline] + pub fn set_length(&mut self, new_len: usize) { + self.bytes.resize(new_len, 0); + } + + pub fn get(&self, pos: usize) -> Option { + self.bytes.get(pos).copied() } pub fn set(&mut self, item: usize, value: u8) { - if self.bytes.len() < (item + 1) { + if self.len() < (item + 1) { self.bytes.resize(item + 1, 0) } @@ -311,38 +228,52 @@ impl ByteArrayStorage { } } + #[inline] pub fn bytes(&self) -> &Vec { &self.bytes } + #[inline] + pub fn len(&self) -> usize { + self.bytes.len() + } + + #[inline] pub fn position(&self) -> usize { - self.position + self.position.get() } - pub fn set_position(&mut self, pos: usize) { - self.position = pos; + #[inline] + pub fn set_position(&self, pos: usize) { + self.position.set(pos); } - pub fn add_position(&mut self, amnt: usize) { - self.position += amnt; + #[inline] + pub fn add_position(&self, amnt: usize) { + self.position.set(self.position.get() + amnt); } + #[inline] pub fn endian(&self) -> &Endian { &self.endian } + #[inline] pub fn set_endian(&mut self, new_endian: Endian) { self.endian = new_endian; } - pub fn len(&self) -> usize { - self.bytes.len() + #[inline] + pub fn bytes_available(&self) -> usize { + self.len().saturating_sub(self.position.get()) } } impl Write for ByteArrayStorage { fn write(&mut self, buf: &[u8]) -> io::Result { - self.write_bytes(buf); + self.write_bytes(buf).map_err(|_| { + io::Error::new(io::ErrorKind::Other, "Failed to write to ByteArrayStorage") + })?; Ok(buf.len()) } @@ -352,6 +283,82 @@ impl Write for ByteArrayStorage { } } +impl Read for ByteArrayStorage { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let bytes = self + .read_bytes(cmp::min(buf.len(), self.bytes_available())) + .map_err(|_| { + io::Error::new(io::ErrorKind::Other, "Failed to read from ByteArrayStorage") + })?; + buf[..bytes.len()].copy_from_slice(bytes); + Ok(bytes.len()) + } +} + +impl Seek for ByteArrayStorage { + fn seek(&mut self, style: SeekFrom) -> io::Result { + let (base_pos, offset) = match style { + SeekFrom::Start(n) => { + self.position.set(n as usize); + return Ok(n); + } + SeekFrom::End(n) => (self.len(), n), + SeekFrom::Current(n) => (self.position.get(), n), + }; + + let new_pos = if offset >= 0 { + base_pos.checked_add(offset as usize) + } else { + base_pos.checked_sub((offset.wrapping_neg()) as usize) + }; + + match new_pos { + Some(n) => { + self.position.set(n); + Ok(n as u64) + } + None => Err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid seek to a negative or overflowing position", + )), + } + } +} + +macro_rules! impl_write{ + ($($method_name:ident $data_type:ty ), *) + => + { + impl ByteArrayStorage { + $( pub fn $method_name (&mut self, val: $data_type) -> Result<(), Error> { + let val_bytes = match self.endian { + Endian::Big => val.to_be_bytes(), + Endian::Little => val.to_le_bytes(), + }; + self.write_bytes(&val_bytes) + } )* + } + } +} + +macro_rules! impl_read{ + ($($method_name:ident $size:expr; $data_type:ty ), *) + => + { + impl ByteArrayStorage { + $( pub fn $method_name (&self) -> Result<$data_type, Error> { + Ok(match self.endian { + Endian::Big => <$data_type>::from_be_bytes(self.read_bytes($size)?.try_into().unwrap()), + Endian::Little => <$data_type>::from_le_bytes(self.read_bytes($size)?.try_into().unwrap()) + }) + } )* + } + } +} + +impl_write!(write_float f32, write_double f64, write_int i32, write_unsigned_int u32, write_short i16, write_unsigned_short u16); +impl_read!(read_float 4; f32, read_double 8; f64, read_int 4; i32, read_unsigned_int 4; u32, read_short 2; i16, read_unsigned_short 2; u16, read_byte 1; i8, read_unsigned_byte 1; u8); + impl Default for ByteArrayStorage { fn default() -> Self { Self::new() diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 1209a0617..a1c396ad2 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -598,6 +598,14 @@ pub fn load_player_globals<'gc>( script, )?; + class( + activation, + flash::utils::compression_algorithm::create_class(mc), + implicit_deriver, + domain, + script, + )?; + function( mc, "flash.utils", diff --git a/core/src/avm2/globals/flash/display/loaderinfo.rs b/core/src/avm2/globals/flash/display/loaderinfo.rs index ae803a35b..24e2def74 100644 --- a/core/src/avm2/globals/flash/display/loaderinfo.rs +++ b/core/src/avm2/globals/flash/display/loaderinfo.rs @@ -307,15 +307,16 @@ pub fn bytes<'gc>( // off. We scroll back 2 bytes before writing the actual // datastream as it is guaranteed to at least be as long as // the implicit end tag we want to get rid of. - let correct_header_length = ba_write.bytes().len() - 2; + let correct_header_length = ba_write.len() - 2; ba_write.set_position(correct_header_length); - ba_write.write_bytes(root.data()); + ba_write.write_bytes(root.data())?; // `swf` wrote the wrong length (since we wrote the data // ourselves), so we need to overwrite it ourselves. ba_write.set_position(4); ba_write.set_endian(Endian::Little); - ba_write.write_unsigned_int((root.data().len() + correct_header_length) as u32); + ba_write + .write_unsigned_int((root.data().len() + correct_header_length) as u32)?; // Finally, reset the array to the correct state. ba_write.set_position(0); diff --git a/core/src/avm2/globals/flash/utils.rs b/core/src/avm2/globals/flash/utils.rs index d3972bea8..e072db698 100644 --- a/core/src/avm2/globals/flash/utils.rs +++ b/core/src/avm2/globals/flash/utils.rs @@ -3,6 +3,7 @@ use crate::avm2::{Activation, Error, Object, Value}; pub mod bytearray; +pub mod compression_algorithm; pub mod endian; /// Implements `flash.utils.getTimer` diff --git a/core/src/avm2/globals/flash/utils/bytearray.rs b/core/src/avm2/globals/flash/utils/bytearray.rs index f281c54f6..eaf05f006 100644 --- a/core/src/avm2/globals/flash/utils/bytearray.rs +++ b/core/src/avm2/globals/flash/utils/bytearray.rs @@ -1,5 +1,5 @@ use crate::avm2::activation::Activation; -use crate::avm2::bytearray::Endian; +use crate::avm2::bytearray::{CompressionAlgorithm, Endian}; use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::method::{Method, NativeMethod}; use crate::avm2::names::{Namespace, QName}; @@ -10,6 +10,7 @@ use crate::avm2::Error; use encoding_rs::Encoding; use encoding_rs::UTF_8; use gc_arena::{GcCell, MutationContext}; +use std::str::FromStr; /// Implements `flash.utils.ByteArray`'s instance constructor. pub fn instance_init<'gc>( @@ -46,7 +47,7 @@ pub fn write_byte<'gc>( .cloned() .unwrap_or(Value::Undefined) .coerce_to_i32(activation)?; - bytearray.write_byte(byte as u8); + bytearray.write_bytes(&[byte as u8])?; } } @@ -85,7 +86,7 @@ pub fn write_bytes<'gc>( &combining_bytes[offset..length + offset] } else { &combining_bytes[offset..] - }); + })?; } } } @@ -132,7 +133,7 @@ pub fn read_bytes<'gc>( ¤t_bytes[position..] }; merging_offset = to_write.len(); - merging_storage.write_bytes_at(to_write, offset); + merging_storage.write_at(to_write, offset)?; } else { return Err("ArgumentError: Parameter must be a bytearray".into()); } @@ -167,7 +168,7 @@ pub fn read_utf<'gc>( _args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { return Ok(AvmString::new(activation.context.gc_context, bytearray.read_utf()?).into()); } } @@ -181,8 +182,7 @@ pub fn to_string<'gc>( ) -> Result, Error> { if let Some(this) = this { if let Some(bytearray) = this.as_bytearray() { - let bytes = bytearray.bytes(); - let (new_string, _, _) = UTF_8.decode(bytes); + let (new_string, _, _) = UTF_8.decode(bytearray.bytes()); return Ok(AvmString::new(activation.context.gc_context, new_string).into()); } } @@ -198,6 +198,7 @@ pub fn clear<'gc>( if let Some(this) = this { if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { bytearray.clear(); + bytearray.shrink_to_fit(); } } @@ -224,7 +225,7 @@ pub fn set_position<'gc>( args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { let num = args .get(0) .unwrap_or(&Value::Integer(0)) @@ -243,13 +244,7 @@ pub fn bytes_available<'gc>( ) -> Result, Error> { if let Some(this) = this { if let Some(bytearray) = this.as_bytearray() { - return Ok(Value::Unsigned( - if bytearray.position() > bytearray.bytes().len() { - 0 - } else { - (bytearray.bytes().len() - bytearray.position()) as u32 - }, - )); + return Ok(Value::Unsigned(bytearray.bytes_available() as u32)); } } @@ -263,7 +258,7 @@ pub fn length<'gc>( ) -> Result, Error> { if let Some(this) = this { if let Some(bytearray) = this.as_bytearray() { - return Ok(Value::Unsigned(bytearray.bytes().len() as u32)); + return Ok(Value::Unsigned(bytearray.len() as u32)); } } @@ -329,12 +324,12 @@ pub fn set_endian<'gc>( } pub fn read_short<'gc>( - activation: &mut Activation<'_, 'gc, '_>, + _activation: &mut Activation<'_, 'gc, '_>, this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { return Ok(Value::Integer(bytearray.read_short()? as i32)); } } @@ -343,12 +338,12 @@ pub fn read_short<'gc>( } pub fn read_unsigned_short<'gc>( - activation: &mut Activation<'_, 'gc, '_>, + _activation: &mut Activation<'_, 'gc, '_>, this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { return Ok(Value::Unsigned(bytearray.read_unsigned_short()? as u32)); } } @@ -357,12 +352,12 @@ pub fn read_unsigned_short<'gc>( } pub fn read_double<'gc>( - activation: &mut Activation<'_, 'gc, '_>, + _activation: &mut Activation<'_, 'gc, '_>, this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { return Ok(Value::Number(bytearray.read_double()?)); } } @@ -371,12 +366,12 @@ pub fn read_double<'gc>( } pub fn read_float<'gc>( - activation: &mut Activation<'_, 'gc, '_>, + _activation: &mut Activation<'_, 'gc, '_>, this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { return Ok(Value::Number(bytearray.read_float()? as f64)); } } @@ -385,12 +380,12 @@ pub fn read_float<'gc>( } pub fn read_int<'gc>( - activation: &mut Activation<'_, 'gc, '_>, + _activation: &mut Activation<'_, 'gc, '_>, this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { return Ok(Value::Integer(bytearray.read_int()?)); } } @@ -399,12 +394,12 @@ pub fn read_int<'gc>( } pub fn read_unsigned_int<'gc>( - activation: &mut Activation<'_, 'gc, '_>, + _activation: &mut Activation<'_, 'gc, '_>, this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { return Ok(Value::Unsigned(bytearray.read_unsigned_int()?)); } } @@ -413,12 +408,12 @@ pub fn read_unsigned_int<'gc>( } pub fn read_boolean<'gc>( - activation: &mut Activation<'_, 'gc, '_>, + _activation: &mut Activation<'_, 'gc, '_>, this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { return Ok(Value::Bool(bytearray.read_boolean()?)); } } @@ -427,12 +422,12 @@ pub fn read_boolean<'gc>( } pub fn read_byte<'gc>( - activation: &mut Activation<'_, 'gc, '_>, + _activation: &mut Activation<'_, 'gc, '_>, this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { return Ok(Value::Integer(bytearray.read_byte()? as i32)); } } @@ -446,14 +441,14 @@ pub fn read_utf_bytes<'gc>( args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { let len = args .get(0) .unwrap_or(&Value::Undefined) .coerce_to_u32(activation)?; return Ok(AvmString::new( activation.context.gc_context, - String::from_utf8_lossy(bytearray.read_exact(len as usize)?), + String::from_utf8_lossy(bytearray.read_bytes(len as usize)?), ) .into()); } @@ -463,12 +458,12 @@ pub fn read_utf_bytes<'gc>( } pub fn read_unsigned_byte<'gc>( - activation: &mut Activation<'_, 'gc, '_>, + _activation: &mut Activation<'_, 'gc, '_>, this: Option>, _args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { return Ok(Value::Unsigned(bytearray.read_unsigned_byte()? as u32)); } } @@ -487,7 +482,7 @@ pub fn write_float<'gc>( .get(0) .unwrap_or(&Value::Undefined) .coerce_to_number(activation)?; - bytearray.write_float(num as f32); + bytearray.write_float(num as f32)?; } } @@ -505,7 +500,7 @@ pub fn write_double<'gc>( .get(0) .unwrap_or(&Value::Undefined) .coerce_to_number(activation)?; - bytearray.write_double(num); + bytearray.write_double(num)?; } } @@ -520,7 +515,7 @@ pub fn write_boolean<'gc>( if let Some(this) = this { if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { let num = args.get(0).unwrap_or(&Value::Undefined).coerce_to_boolean(); - bytearray.write_boolean(num); + bytearray.write_boolean(num)?; } } @@ -538,7 +533,7 @@ pub fn write_int<'gc>( .get(0) .unwrap_or(&Value::Undefined) .coerce_to_i32(activation)?; - bytearray.write_int(num); + bytearray.write_int(num)?; } } @@ -556,7 +551,7 @@ pub fn write_unsigned_int<'gc>( .get(0) .unwrap_or(&Value::Undefined) .coerce_to_u32(activation)?; - bytearray.write_unsigned_int(num); + bytearray.write_unsigned_int(num)?; } } @@ -574,7 +569,7 @@ pub fn write_short<'gc>( .get(0) .unwrap_or(&Value::Undefined) .coerce_to_i32(activation)?; - bytearray.write_short(num as i16); + bytearray.write_short(num as i16)?; } } @@ -598,7 +593,7 @@ pub fn write_multibyte<'gc>( .coerce_to_string(activation)?; let encoder = Encoding::for_label(charset_label.as_bytes()).unwrap_or(UTF_8); let (encoded_bytes, _, _) = encoder.encode(string.as_str()); - bytearray.write_bytes(&encoded_bytes.into_owned()); + bytearray.write_bytes(&encoded_bytes.into_owned())?; } } @@ -611,7 +606,7 @@ pub fn read_multibyte<'gc>( args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { - if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { + if let Some(bytearray) = this.as_bytearray() { let len = args .get(0) .unwrap_or(&Value::Undefined) @@ -620,7 +615,7 @@ pub fn read_multibyte<'gc>( .get(1) .unwrap_or(&"UTF-8".into()) .coerce_to_string(activation)?; - let bytes = bytearray.read_exact(len as usize)?; + let bytes = bytearray.read_bytes(len as usize)?; let encoder = Encoding::for_label(charset_label.as_bytes()).unwrap_or(UTF_8); let (decoded_str, _, _) = encoder.decode(bytes); return Ok(AvmString::new(activation.context.gc_context, decoded_str).into()); @@ -641,7 +636,7 @@ pub fn write_utf_bytes<'gc>( .get(0) .unwrap_or(&Value::Undefined) .coerce_to_string(activation)?; - bytearray.write_bytes(string.as_bytes()); + bytearray.write_bytes(string.as_bytes())?; } } @@ -655,17 +650,13 @@ pub fn compress<'gc>( ) -> Result, Error> { if let Some(this) = this { if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { - if let Value::String(string) = args.get(0).unwrap_or(&Value::Undefined) { - let compressed = match string.as_str() { - "zlib" => bytearray.zlib_compress(), - "deflate" => bytearray.deflate_compress(), - &_ => return Ok(Value::Undefined), - }; - if let Ok(buffer) = compressed { - bytearray.clear(); - bytearray.write_bytes(&buffer); - } - } + let algorithm = args + .get(0) + .unwrap_or(&"zlib".into()) + .coerce_to_string(activation)?; + let buffer = bytearray.compress(CompressionAlgorithm::from_str(algorithm.as_str())?)?; + bytearray.clear(); + bytearray.write_bytes(&buffer)?; } } @@ -679,17 +670,14 @@ pub fn uncompress<'gc>( ) -> Result, Error> { if let Some(this) = this { if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { - if let Value::String(string) = args.get(0).unwrap_or(&Value::Undefined) { - let compressed = match string.as_str() { - "zlib" => bytearray.zlib_decompress(), - "deflate" => bytearray.deflate_decompress(), - &_ => return Ok(Value::Undefined), - }; - if let Ok(buffer) = compressed { - bytearray.clear(); - bytearray.write_bytes(&buffer); - } - } + let algorithm = args + .get(0) + .unwrap_or(&"zlib".into()) + .coerce_to_string(activation)?; + let buffer = + bytearray.decompress(CompressionAlgorithm::from_str(algorithm.as_str())?)?; + bytearray.clear(); + bytearray.write_bytes(&buffer)?; } } @@ -703,10 +691,9 @@ pub fn deflate<'gc>( ) -> Result, Error> { if let Some(this) = this { if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { - if let Ok(buffer) = bytearray.deflate_compress() { - bytearray.clear(); - bytearray.write_bytes(&buffer); - } + let buffer = bytearray.compress(CompressionAlgorithm::Deflate)?; + bytearray.clear(); + bytearray.write_bytes(&buffer)?; } } @@ -720,10 +707,9 @@ pub fn inflate<'gc>( ) -> Result, Error> { if let Some(this) = this { if let Some(mut bytearray) = this.as_bytearray_mut(activation.context.gc_context) { - if let Ok(buffer) = bytearray.deflate_decompress() { - bytearray.clear(); - bytearray.write_bytes(&buffer); - } + let buffer = bytearray.decompress(CompressionAlgorithm::Deflate)?; + bytearray.clear(); + bytearray.write_bytes(&buffer)?; } } diff --git a/core/src/avm2/globals/flash/utils/compression_algorithm.rs b/core/src/avm2/globals/flash/utils/compression_algorithm.rs new file mode 100644 index 000000000..e867a97df --- /dev/null +++ b/core/src/avm2/globals/flash/utils/compression_algorithm.rs @@ -0,0 +1,46 @@ +use crate::avm2::activation::Activation; +use crate::avm2::class::{Class, ClassAttributes}; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; +use crate::avm2::value::Value; +use crate::avm2::Error; +use gc_arena::{GcCell, MutationContext}; + +/// Implements `flash.utils.CompressionAlgorithm`'s instance constructor. +pub fn instance_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Implements `flash.utils.CompressionAlgorithm`'s class constructor. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + let class = Class::new( + QName::new(Namespace::package("flash.utils"), "CompressionAlgorithm"), + None, + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ); + + let mut write = class.write(mc); + + write.set_attributes(ClassAttributes::FINAL | ClassAttributes::SEALED); + + const CONSTANTS: &[(&str, &str)] = + &[("DEFLATE", "deflate"), ("LZMA", "lzma"), ("ZLIB", "zlib")]; + write.define_public_constant_string_class_traits(CONSTANTS); + + class +} diff --git a/tests/tests/swfs/avm2/bytearray/Test.as b/tests/tests/swfs/avm2/bytearray/Test.as index f08c8580c..4ccf6da82 100644 --- a/tests/tests/swfs/avm2/bytearray/Test.as +++ b/tests/tests/swfs/avm2/bytearray/Test.as @@ -6,42 +6,83 @@ package { import flash.utils.ByteArray; import flash.utils.Endian; +import flash.utils.CompressionAlgorithm; -var test = new ByteArray(); -test.writeUTFBytes("HELLO TEST"); -test.position = 0; -trace(test.readByte()); -trace(test.readByte()); -trace(test.readByte()); -trace(test.readByte()); -trace(test.readFloat()); -test.position -= 4; -test.endian = Endian.LITTLE_ENDIAN; -trace(test.readFloat()); -test.clear(); -test.writeUTFBytes("Test"); -test.position = 0; -trace(test.readUTFBytes(4)); -test.position = 3; -test.writeBytes(test); -trace(test.toString()); -test.position = 0; -var test2 = new ByteArray(); -test.readBytes(test2); -trace(test2.toString()); -trace(test2.position); -trace(test.position); -trace(test2.bytesAvailable); -trace(test2.readMultiByte(5, "shift-jis")); -trace(test2.readShort()); -test2.clear(); -test2.writeMultiByte("次 滋 治 爾 璽 痔 磁 示 而 耳 自 蒔 辞 汐 鹿 ", "shift-jis"); -test2.position = 0; -trace(test2.readMultiByte(6, "shift-jis")); -test2.clear(); -test2.writeUTF("THIS IS A TEST UTF STRING"); -test2.position = 0; -trace(test2.readUTF()); -trace(test2[1]); -test2[0] = 90; -trace(test2.toString()); \ No newline at end of file +var ba1 = new ByteArray(); +var ba2 = new ByteArray(); +ba1.writeUTFBytes("test data"); +ba2.writeUTFBytes("more data"); +ba1.writeBytes(ba2, 3, 2); +trace("// ba1.writeBytes(ba2, 3, 2);"); +trace(ba1); +ba1.position = 0; +ba1.readBytes(ba2, 2, 3); +trace("// ba1.readBytes(ba2, 2, 3);"); +trace(ba2); +ba1.position = 0; +ba1.position = 100; +ba1.writeUnsignedInt(2); +trace("// ba1.position = 100;"); +trace("// ba1.writeUnsignedInt(2);"); +trace(ba1.length); +ba1.clear(); +trace("// ba1.clear();"); +trace(ba1.length); +ba1.writeDouble(6.6); +ba1.position = 3; +trace("// ba1.writeDouble(6.6);"); +trace("// ba1.position = 3;") +trace("// ba1.bytesAvailable;") +trace(ba1.bytesAvailable); +ba1.clear(); +ba1.writeMultiByte("次 滋 治 爾 璽 痔 磁 示 而 耳 自 蒔 辞 汐 鹿 ", "shift-jis"); +trace("// ba1.writeMultiByte(\"次 滋 治 爾 璽 痔 磁 示 而 耳 自 蒔 辞 汐 鹿 \", \"shift-jis\");"); +ba1.position = 0; +trace("// ba1.readMultiByte(6, \"shift-jis\")"); +trace(ba1.readMultiByte(6, "shift-jis")); +ba1.clear(); +ba1.writeFloat(3); +ba1.writeDouble(5); +ba1.writeInt(-10); +ba1.writeUnsignedInt(20); +ba1.writeShort(40); +ba1.writeShort(22); +ba1.writeBoolean(false); +ba1.writeBoolean(true); +ba1.writeBoolean(10); +ba1.writeByte(100); +ba1.writeByte(255); +ba1.position = 0; +trace(ba1.readFloat()); +trace(ba1.readDouble()); +trace(ba1.readInt()); +trace(ba1.readUnsignedInt()); +trace(ba1.readShort()); +trace(ba1.readUnsignedShort()); +trace(ba1.readBoolean()); +trace(ba1.readBoolean()); +trace(ba1.readBoolean()); +trace(ba1.readByte()); +trace(ba1.readUnsignedByte()); +ba1.clear(); +ba1.writeFloat(3); +ba1.writeDouble(5); +ba1.writeInt(-10); +ba1.writeUnsignedInt(20); +ba1.writeShort(40); +ba1.writeShort(22); +ba1.writeBoolean(false); +ba1.writeBoolean(true); +ba1.writeBoolean(10); +ba1.writeByte(100); +ba1.writeByte(255); +ba1.position = 0; +trace(ba1.readUnsignedByte()); +trace(ba1.readByte()); +trace(ba1.readBoolean()); +trace(ba1.readBoolean()); +trace(ba1.readBoolean()); +trace(ba1.readUnsignedShort()); +trace(ba1.readShort()); +trace(ba1.readUnsignedInt()); +trace(ba1.readInt()); \ No newline at end of file diff --git a/tests/tests/swfs/avm2/bytearray/output.txt b/tests/tests/swfs/avm2/bytearray/output.txt index 8518607bc..b67901f8d 100644 --- a/tests/tests/swfs/avm2/bytearray/output.txt +++ b/tests/tests/swfs/avm2/bytearray/output.txt @@ -1,18 +1,36 @@ -72 -69 -76 -76 -2689877248 -3394.019287109375 -Test -TesTest -TesTest -0 -7 -7 -TesTe -29556 -次 滋 -THIS IS A TEST UTF STRING -25 -ZTHIS IS A TEST UTF STRING \ No newline at end of file +// ba1.writeBytes(ba2, 3, 2); +test datae +// ba1.readBytes(ba2, 2, 3); +motesdata +// ba1.position = 100; +// ba1.writeUnsignedInt(2); +104 +// ba1.clear(); +0 +// ba1.writeDouble(6.6); +// ba1.position = 3; +// ba1.bytesAvailable; +5 +// ba1.writeMultiByte("次 滋 治 爾 璽 痔 磁 示 而 耳 自 蒔 辞 汐 鹿 ", "shift-jis"); +// ba1.readMultiByte(6, "shift-jis") +次 滋 +3 +5 +-10 +20 +40 +22 +false +true +true +100 +255 +64 +64 +false +false +true +5120 +0 +255 +-2560 diff --git a/tests/tests/swfs/avm2/bytearray/test.swf b/tests/tests/swfs/avm2/bytearray/test.swf index c3e60c7f65172d070dc0380802bdf0a0e513ce47..7bf80a08f5895079f341abb95350d245a28a44c7 100644 GIT binary patch literal 1679 zcmV;A25|X9S5pv54FCXmoV8ZXZyU!IpWPvMIix;gnO1Bo8Pl{aB_#Jl62%Wm4MmEr z0|+cE1;|ARSKOhw>2jCYU45PUacP0}Py_*bD_XRtpqHelCP@4*sE4Bef#v=KJ@n$v z%#ze9R)nNM*XF*rZ+`Fh-n@PDRwp3-0RYo~0AOAOjoEnsz)x??!~t0M%x>jLqlr)Y zw&Pdm(?+Hj1cQpA93LMSj+YCr_f*l!<+7scimvA=BJZC%fpL;|{QH?r%o!TQ?|9ZA zuv`bTXXC&<3^p>EA?-J`q|kBu%84;hw1TQI9tx_|Jz@l|x97U{W{sh6 z(>DAb{;}tFiSJW-!^ZX9BF^K3L(4Yt>hjvU@^#M$7irMnrmpH*Ue)tzX-`|LENd0L zNcD=UMtnnL=(^N& z+nt^3tK07@BeWkpA>HfI{@&?;C{Kv*9(o-@;rEAcXJ@DK*zp6y>5#{bO=>7umRVV; zZ>i0dt<~~sy|KNeX|>YoN_~4(t8eLLO)E9{MlZXL*cxuAwutM9t>sxy zE!rPfu&$NWQlqYEdb3z#tXq1c+^Cg`gdWo^?dbrswwgTNoIgPgtck zGNV$lGV%IWtXIoauh&XiX}ex3ebdHXaUHRlS8YfuuGVU6#Zr;8m1--s`u1|ErmvOj zO?_*`W=1y>n%wVGrf;Oc;}>&Lr~kR7&Ca+(2Zx@`N08Z3h)q~DpLVInCx+RnbX~7+ z1e?ZSU|Su75h^Enzvp&-a%>!te3wm+b>#whWlG9$2yS9f7hk@7`Rrzj4oCs)c8Ei; z2Eenqf6B~2uNhFFcd^T_+wW|G*dFl%sdexn=>&;@>L1Xc6FY|G>{)$cTMn7zSb5nwIYL*WXsPQNeWJkwgZ=bW=zJJhwvV<>15)!mO2S+*8#|N7o)wVy z_L^+Xzc(z~#i_3CE-SjK78N!x1Is~u*CX6B$vwGSJn)PTSx^*AqZTggi!^~8Ud~~C zshSwLzQqcL=(IHX^4+1s*bw5G%TjeFOw94Erw%b6J3)Gg9`WD}tf^}CPBdx+zDQ$A zHgTQ?y?DnahBrL~@`uHxYKnU_+`|K#%tuK=(_*nuT&m8!8g#ij%H)9MTstx>+n^5B zsmssn&;RmA{QQewje{GPtcDmF&s2*5Z(&hDao|3@G?`W`!ti0 z7s|n$Z8o~XEh*kYA$?-hPBT#$GfWvK9W3;8?DE4q5grY4#D>E3HWLkc#*CoTBi>{q zJZ|B^oTc#z?eT!lrqGhUv|e}VUo`aadD8BHZ7!7-=EZa@EzKux#o{16k(f!BVuB#Z zP>@kh7DQQqGK|YHS(cD2CuKQ>L;)p5IWdV+BASNiH7E-=&`pTa5Y0n$2co+Wy#Y}M zq6ZKyLbL?YTM)esQ4XR!L06-v$P@sy$Ae=xFAl;J;1S;W~`a?S61R)le&eXHVfIf*a zQGox6ovHhYwy3tn_rXF>*hl-xcay-7_EVuEx24=vYeHztxmhNpww#4ou{8hWU5v zbtdL$o>VNhZZnbQ;!*1ky+7plY%&yP>n;@!UJ6`>vD#8|0Sv=6-xzCejx{{i?v1s~ zSi66%mK|#k!2fv@53UG$eR7#AHijbwXif>g_{ZOWM7vg?hbrt%+N#MEqRlA0D}W_x zh(l_a;mx}LFbmGQ@zz6;+9i=XJvvhfU|su2Z7-_rCAIyQ+J0MY$LRm}g@y3yK??uD ZjoTpnxR1Wah3NMfyhHy1@LzBJv5bSDQ1bu) literal 1446 zcmV;X1zGw-S5pyg3IG6joTXJsZ`(!`9xmdDvg9R>6=!iWv6ZB0Es>OCSrQz#vX(@F z6&02W1O<#}ERG_k6e*DOcIt9zf%Z@Y0eUMMw5Ot%wx>2|@)z7gFZ}_w`x|;lhos~s z8{C$b#K(Ex`{vE}9-lWs;4T0|-vBVqg2Ko+0N}&X;Q#>hmfFfL7fNWeqZ@XXK3$(| zJ5Dz%NgEp*iH(_rY2A~QOeQ1ANhz6(Q$*a}G90`aH|(j&1>`Xlh~2cbuA`X-a-Z?4 zx#nD-ob1tVcDnnthMmBwxk?gEvmpJ-Y~CW+F|E33>I*p+g-SZM+vr`( zY!Tb0^jJsvS_;L{>YApjXm)09UV1+A5Em)X??O^eDsed(m(z7+E<2-SlPPLvW%*F7 zkMu(;#K9_d@R498Syzrwe&S zNtRMMmvu2&$P{wvR3?>HO4k-=`>Zda*w2+%+B#87mf7*D(8ZQbT&1p0_N8)V^7PGW zFUmBRQBt$H+*~UCs)M}{+GkT=cA(?2sfQ2ZFPZ;GrdLv+{lXmR^q*T=ZSHqycg@nh z5u`RHq7!#Fn|7(YnSPjbMIUvo6w78kc1 zlCvzl6}nkmTB@RYu~v_4SeirLtCw7-eX%Fm!5f{c%}7aEPD$>pbTlK_HEqp3bHK51 zlLYD5Q{7O}HA=dP9ieEb8a5(J<$8Uoc(-`FP|n>Jh_@JM>I7S17u(-5gihs{5N;$C{2;bt2krt>wh;Yj(tiRMu!p`@7fLrsd4<{O}0vJpLT*{PY;@ ze)a_IKKKpoe)SOTe)}=n{q9$^_t6*h`!m}6^n0}T;33+3@-^D|@k{jAFHg~+m&-ou z>H5ub4be}|lM2z4QLVmQzI`LuR}J=#GKf{$uT=b+*Aro1cCzmF+ECHG6=(^u(`*wf zQt+$k*L|dHID|^+(3baCqDTCBlm4TmwY7v=*WGj=%8av7J}Qg{Pw@c|4F`v#S9peD zAjd%QGGtiDaF7c?9zsEcFa+U%$TDJxg~5n8#EByuWKM{qoOqIh!7<20#Z#O(&WWcv z@eC)P<-`G2JjaO>oOpo~5hqS^VvG|nac61VWf*z`@>e1MCgk6O{4@lwyI6)5S=#a( z9}Iv16JP*+13194977Gkg?I!4!_fmkz|cFlE#IK83B&LKVO!oV1Ny}BEW`cDZp$md z1}it%J7A*CtcWY2TOojjhA=%?9bg(l4B+req#=|BK{di0!y0m}1|?{PWBel=rsi;K z1fMwUtd0V@0efVF7+*Tcc-|Q2MG4h0pE2qOdB9VCevtZMb=>ts-|t$ja4(6z0b8eg zMl3LWzI2A|J(bRSl+eusPG6ipUp>cCjGqaN34LrIF;$&l4*aF_{S9MG>w*W1f!O=_ zLNA+Ag!$x;-*3{YFtkH_?P)1O10ofhxy691bRodeg<-E4D<}rGTY)NK>AJwupex%l z0d7?;%8g06F(o%*a^sTR;OXZMGvQxtj>6v_Jp=p!*q>dA!?O##L;nEy2Vh;}Hsim} A`~Uy|