swf: Add SwfStr type to handle encodings in SWF<6
This commit is contained in:
parent
19034b76e4
commit
1d9c11e145
|
@ -3439,6 +3439,7 @@ version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"approx",
|
"approx",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"encoding_rs",
|
||||||
"enumset",
|
"enumset",
|
||||||
"flate2",
|
"flate2",
|
||||||
"libflate 1.0.3",
|
"libflate 1.0.3",
|
||||||
|
|
|
@ -25,6 +25,7 @@ use std::convert::TryFrom;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use swf::avm1::read::Reader;
|
use swf::avm1::read::Reader;
|
||||||
use swf::avm1::types::{Action, CatchVar, Function, TryBlock};
|
use swf::avm1::types::{Action, CatchVar, Function, TryBlock};
|
||||||
|
use swf::SwfStr;
|
||||||
use url::form_urlencoded;
|
use url::form_urlencoded;
|
||||||
|
|
||||||
macro_rules! avm_debug {
|
macro_rules! avm_debug {
|
||||||
|
@ -432,8 +433,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_actions(&mut self, code: SwfSlice) -> Result<ReturnType<'gc>, Error<'gc>> {
|
pub fn run_actions(&mut self, code: SwfSlice) -> Result<ReturnType<'gc>, Error<'gc>> {
|
||||||
let mut read = Reader::new(&code.movie.data()[..], self.swf_version());
|
let mut read = Reader::new(&code.movie.data()[code.start..], self.swf_version());
|
||||||
read.seek(code.start as isize);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let result = self.do_action(&code, &mut read);
|
let result = self.do_action(&code, &mut read);
|
||||||
|
@ -446,10 +446,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a single action from a given action reader.
|
/// Run a single action from a given action reader.
|
||||||
fn do_action(
|
fn do_action<'b>(
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &SwfSlice,
|
data: &'b SwfSlice,
|
||||||
reader: &mut Reader<'_>,
|
reader: &mut Reader<'b>,
|
||||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||||
self.actions_since_timeout_check += 1;
|
self.actions_since_timeout_check += 1;
|
||||||
if self.actions_since_timeout_check >= 200 {
|
if self.actions_since_timeout_check >= 200 {
|
||||||
|
@ -459,7 +459,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if reader.pos() >= data.end {
|
if reader.get_ref().as_ptr() as usize >= data.as_ref().as_ptr_range().end as usize {
|
||||||
//Executing beyond the end of a function constitutes an implicit return.
|
//Executing beyond the end of a function constitutes an implicit return.
|
||||||
Ok(FrameControl::Return(ReturnType::Implicit))
|
Ok(FrameControl::Return(ReturnType::Implicit))
|
||||||
} else if let Some(action) = reader.read_action()? {
|
} else if let Some(action) = reader.read_action()? {
|
||||||
|
@ -496,7 +496,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
params,
|
params,
|
||||||
actions,
|
actions,
|
||||||
} => self.action_define_function(
|
} => self.action_define_function(
|
||||||
&name,
|
name,
|
||||||
¶ms[..],
|
¶ms[..],
|
||||||
data.to_unbounded_subslice(actions).unwrap(),
|
data.to_unbounded_subslice(actions).unwrap(),
|
||||||
),
|
),
|
||||||
|
@ -516,7 +516,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
Action::GetProperty => self.action_get_property(),
|
Action::GetProperty => self.action_get_property(),
|
||||||
Action::GetTime => self.action_get_time(),
|
Action::GetTime => self.action_get_time(),
|
||||||
Action::GetVariable => self.action_get_variable(),
|
Action::GetVariable => self.action_get_variable(),
|
||||||
Action::GetUrl { url, target } => self.action_get_url(&url, &target),
|
Action::GetUrl { url, target } => self.action_get_url(url, target),
|
||||||
Action::GetUrl2 {
|
Action::GetUrl2 {
|
||||||
send_vars_method,
|
send_vars_method,
|
||||||
is_target_sprite,
|
is_target_sprite,
|
||||||
|
@ -528,14 +528,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
scene_offset,
|
scene_offset,
|
||||||
} => self.action_goto_frame_2(set_playing, scene_offset),
|
} => self.action_goto_frame_2(set_playing, scene_offset),
|
||||||
Action::Greater => self.action_greater(),
|
Action::Greater => self.action_greater(),
|
||||||
Action::GotoLabel(label) => self.action_goto_label(&label),
|
Action::GotoLabel(label) => self.action_goto_label(label),
|
||||||
Action::If { offset } => self.action_if(offset, reader),
|
Action::If { offset } => self.action_if(offset, reader, data),
|
||||||
Action::Increment => self.action_increment(),
|
Action::Increment => self.action_increment(),
|
||||||
Action::InitArray => self.action_init_array(),
|
Action::InitArray => self.action_init_array(),
|
||||||
Action::InitObject => self.action_init_object(),
|
Action::InitObject => self.action_init_object(),
|
||||||
Action::ImplementsOp => self.action_implements_op(),
|
Action::ImplementsOp => self.action_implements_op(),
|
||||||
Action::InstanceOf => self.action_instance_of(),
|
Action::InstanceOf => self.action_instance_of(),
|
||||||
Action::Jump { offset } => self.action_jump(offset, reader),
|
Action::Jump { offset } => self.action_jump(offset, reader, data),
|
||||||
Action::Less => self.action_less(),
|
Action::Less => self.action_less(),
|
||||||
Action::Less2 => self.action_less_2(),
|
Action::Less2 => self.action_less_2(),
|
||||||
Action::MBAsciiToChar => self.action_mb_ascii_to_char(),
|
Action::MBAsciiToChar => self.action_mb_ascii_to_char(),
|
||||||
|
@ -559,7 +559,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
Action::Return => self.action_return(),
|
Action::Return => self.action_return(),
|
||||||
Action::SetMember => self.action_set_member(),
|
Action::SetMember => self.action_set_member(),
|
||||||
Action::SetProperty => self.action_set_property(),
|
Action::SetProperty => self.action_set_property(),
|
||||||
Action::SetTarget(target) => self.action_set_target(&target),
|
Action::SetTarget(target) => self.action_set_target(&target.to_str_lossy()),
|
||||||
Action::SetTarget2 => self.action_set_target2(),
|
Action::SetTarget2 => self.action_set_target2(),
|
||||||
Action::SetVariable => self.action_set_variable(),
|
Action::SetVariable => self.action_set_variable(),
|
||||||
Action::StackSwap => self.action_stack_swap(),
|
Action::StackSwap => self.action_stack_swap(),
|
||||||
|
@ -889,11 +889,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
|
|
||||||
fn action_constant_pool(
|
fn action_constant_pool(
|
||||||
&mut self,
|
&mut self,
|
||||||
constant_pool: &[&str],
|
constant_pool: &[SwfStr<'_>],
|
||||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||||
self.context.avm1.constant_pool = GcCell::allocate(
|
self.context.avm1.constant_pool = GcCell::allocate(
|
||||||
self.context.gc_context,
|
self.context.gc_context,
|
||||||
constant_pool.iter().map(|s| (*s).to_string()).collect(),
|
constant_pool
|
||||||
|
.iter()
|
||||||
|
.map(|s| (*s).to_string_lossy())
|
||||||
|
.collect(),
|
||||||
);
|
);
|
||||||
self.set_constant_pool(self.context.avm1.constant_pool);
|
self.set_constant_pool(self.context.avm1.constant_pool);
|
||||||
|
|
||||||
|
@ -908,10 +911,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
|
|
||||||
fn action_define_function(
|
fn action_define_function(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: SwfStr<'_>,
|
||||||
params: &[&str],
|
params: &[SwfStr<'_>],
|
||||||
actions: SwfSlice,
|
actions: SwfSlice,
|
||||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||||
|
let name = name.to_str_lossy();
|
||||||
|
let name = name.as_ref();
|
||||||
let swf_version = self.swf_version();
|
let swf_version = self.swf_version();
|
||||||
let scope = Scope::new_closure_scope(self.scope_cell(), self.context.gc_context);
|
let scope = Scope::new_closure_scope(self.scope_cell(), self.context.gc_context);
|
||||||
let constant_pool = self.constant_pool();
|
let constant_pool = self.constant_pool();
|
||||||
|
@ -977,7 +982,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
if action_func.name.is_empty() {
|
if action_func.name.is_empty() {
|
||||||
self.context.avm1.push(func_obj);
|
self.context.avm1.push(func_obj);
|
||||||
} else {
|
} else {
|
||||||
self.define(action_func.name, func_obj);
|
self.define(&action_func.name.to_str_lossy(), func_obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(FrameControl::Continue)
|
Ok(FrameControl::Continue)
|
||||||
|
@ -1213,9 +1218,15 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
Ok(FrameControl::Continue)
|
Ok(FrameControl::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_get_url(&mut self, url: &str, target: &str) -> Result<FrameControl<'gc>, Error<'gc>> {
|
fn action_get_url(
|
||||||
|
&mut self,
|
||||||
|
url: SwfStr<'_>,
|
||||||
|
target: SwfStr<'_>,
|
||||||
|
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||||
|
let target = target.to_str_lossy();
|
||||||
|
let target = target.as_ref();
|
||||||
|
let url = url.to_string_lossy();
|
||||||
if target.starts_with("_level") && target.len() > 6 {
|
if target.starts_with("_level") && target.len() > 6 {
|
||||||
let url = url.to_string();
|
|
||||||
match target[6..].parse::<u32>() {
|
match target[6..].parse::<u32>() {
|
||||||
Ok(level_id) => {
|
Ok(level_id) => {
|
||||||
let fetch = self.context.navigator.fetch(&url, RequestOptions::get());
|
let fetch = self.context.navigator.fetch(&url, RequestOptions::get());
|
||||||
|
@ -1247,7 +1258,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
return Ok(FrameControl::Continue);
|
return Ok(FrameControl::Continue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(fscommand) = fscommand::parse(url) {
|
if let Some(fscommand) = fscommand::parse(&url) {
|
||||||
fscommand::handle(fscommand, self)?;
|
fscommand::handle(fscommand, self)?;
|
||||||
} else {
|
} else {
|
||||||
self.context
|
self.context
|
||||||
|
@ -1414,10 +1425,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
Ok(FrameControl::Continue)
|
Ok(FrameControl::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_goto_label(&mut self, label: &str) -> Result<FrameControl<'gc>, Error<'gc>> {
|
fn action_goto_label(&mut self, label: SwfStr<'_>) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||||
if let Some(clip) = self.target_clip() {
|
if let Some(clip) = self.target_clip() {
|
||||||
if let Some(clip) = clip.as_movie_clip() {
|
if let Some(clip) = clip.as_movie_clip() {
|
||||||
if let Some(frame) = clip.frame_label_to_number(label) {
|
if let Some(frame) = clip.frame_label_to_number(&label.to_str_lossy()) {
|
||||||
clip.goto_frame(&mut self.context, frame, true);
|
clip.goto_frame(&mut self.context, frame, true);
|
||||||
} else {
|
} else {
|
||||||
avm_warn!(self, "GoToLabel: Frame label '{}' not found", label);
|
avm_warn!(self, "GoToLabel: Frame label '{}' not found", label);
|
||||||
|
@ -1431,14 +1442,15 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
Ok(FrameControl::Continue)
|
Ok(FrameControl::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_if(
|
fn action_if<'b>(
|
||||||
&mut self,
|
&mut self,
|
||||||
jump_offset: i16,
|
jump_offset: i16,
|
||||||
reader: &mut Reader<'_>,
|
reader: &mut Reader<'b>,
|
||||||
|
data: &'b SwfSlice,
|
||||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||||
let val = self.context.avm1.pop();
|
let val = self.context.avm1.pop();
|
||||||
if val.as_bool(self.current_swf_version()) {
|
if val.as_bool(self.current_swf_version()) {
|
||||||
reader.seek(jump_offset.into());
|
self.seek(jump_offset, reader, data)?;
|
||||||
}
|
}
|
||||||
Ok(FrameControl::Continue)
|
Ok(FrameControl::Continue)
|
||||||
}
|
}
|
||||||
|
@ -1515,13 +1527,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
Ok(FrameControl::Continue)
|
Ok(FrameControl::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_jump(
|
fn action_jump<'b>(
|
||||||
&mut self,
|
&mut self,
|
||||||
jump_offset: i16,
|
jump_offset: i16,
|
||||||
reader: &mut Reader<'_>,
|
reader: &mut Reader<'b>,
|
||||||
|
data: &'b SwfSlice,
|
||||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||||
// TODO(Herschel): Handle out-of-bounds.
|
self.seek(jump_offset, reader, data)?;
|
||||||
reader.seek(jump_offset.into());
|
|
||||||
Ok(FrameControl::Continue)
|
Ok(FrameControl::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1764,12 +1776,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
SwfValue::Float(v) => f64::from(*v).into(),
|
SwfValue::Float(v) => f64::from(*v).into(),
|
||||||
SwfValue::Double(v) => (*v).into(),
|
SwfValue::Double(v) => (*v).into(),
|
||||||
SwfValue::Str(v) => {
|
SwfValue::Str(v) => {
|
||||||
AvmString::new(self.context.gc_context, (*v).to_string()).into()
|
AvmString::new(self.context.gc_context, v.to_string_lossy()).into()
|
||||||
}
|
}
|
||||||
SwfValue::Register(v) => self.current_register(*v),
|
SwfValue::Register(v) => self.current_register(*v),
|
||||||
SwfValue::ConstantPool(i) => {
|
SwfValue::ConstantPool(i) => {
|
||||||
if let Some(value) = self.constant_pool().read().get(*i as usize) {
|
if let Some(value) = self.constant_pool().read().get(*i as usize) {
|
||||||
AvmString::new(self.context.gc_context, value.to_string()).into()
|
AvmString::new(self.context.gc_context, value).into()
|
||||||
} else {
|
} else {
|
||||||
avm_warn!(
|
avm_warn!(
|
||||||
self,
|
self,
|
||||||
|
@ -2267,7 +2279,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
);
|
);
|
||||||
|
|
||||||
match catch_vars {
|
match catch_vars {
|
||||||
CatchVar::Var(name) => activation.set_variable(name, value.to_owned())?,
|
CatchVar::Var(name) => {
|
||||||
|
activation.set_variable(&name.to_str_lossy(), value.to_owned())?
|
||||||
|
}
|
||||||
CatchVar::Register(id) => {
|
CatchVar::Register(id) => {
|
||||||
activation.set_current_register(*id, value.to_owned())
|
activation.set_current_register(*id, value.to_owned())
|
||||||
}
|
}
|
||||||
|
@ -2968,4 +2982,18 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
||||||
Ok(FrameControl::Continue)
|
Ok(FrameControl::Continue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn seek<'b>(
|
||||||
|
&self,
|
||||||
|
jump_offset: i16,
|
||||||
|
reader: &mut Reader<'b>,
|
||||||
|
data: &'b SwfSlice,
|
||||||
|
) -> Result<(), Error<'gc>> {
|
||||||
|
let slice = data.movie.data();
|
||||||
|
let mut pos = reader.get_ref().as_ptr() as usize - slice.as_ptr() as usize;
|
||||||
|
pos = (pos as isize + isize::from(jump_offset)) as usize;
|
||||||
|
pos = pos.min(slice.len());
|
||||||
|
*reader.get_mut() = &slice[pos as usize..];
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use enumset::EnumSet;
|
||||||
use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext};
|
use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use swf::avm1::types::FunctionParam;
|
use swf::{avm1::types::FunctionParam, SwfStr};
|
||||||
|
|
||||||
/// Represents a function defined in Ruffle's code.
|
/// Represents a function defined in Ruffle's code.
|
||||||
///
|
///
|
||||||
|
@ -94,14 +94,15 @@ impl<'gc> Avm1Function<'gc> {
|
||||||
swf_version: u8,
|
swf_version: u8,
|
||||||
actions: SwfSlice,
|
actions: SwfSlice,
|
||||||
name: &str,
|
name: &str,
|
||||||
params: &[&str],
|
params: &[SwfStr<'_>],
|
||||||
scope: GcCell<'gc, Scope<'gc>>,
|
scope: GcCell<'gc, Scope<'gc>>,
|
||||||
constant_pool: GcCell<'gc, Vec<String>>,
|
constant_pool: GcCell<'gc, Vec<String>>,
|
||||||
base_clip: DisplayObject<'gc>,
|
base_clip: DisplayObject<'gc>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let name = match name {
|
let name = if name.is_empty() {
|
||||||
"" => None,
|
None
|
||||||
name => Some(name.to_string()),
|
} else {
|
||||||
|
Some(name.to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
Avm1Function {
|
Avm1Function {
|
||||||
|
@ -118,7 +119,10 @@ impl<'gc> Avm1Function<'gc> {
|
||||||
suppress_this: false,
|
suppress_this: false,
|
||||||
preload_this: false,
|
preload_this: false,
|
||||||
preload_global: false,
|
preload_global: false,
|
||||||
params: params.iter().map(|&s| (None, s.to_string())).collect(),
|
params: params
|
||||||
|
.iter()
|
||||||
|
.map(|&s| (None, s.to_string_lossy()))
|
||||||
|
.collect(),
|
||||||
scope,
|
scope,
|
||||||
constant_pool,
|
constant_pool,
|
||||||
base_clip,
|
base_clip,
|
||||||
|
@ -134,9 +138,10 @@ impl<'gc> Avm1Function<'gc> {
|
||||||
constant_pool: GcCell<'gc, Vec<String>>,
|
constant_pool: GcCell<'gc, Vec<String>>,
|
||||||
base_clip: DisplayObject<'gc>,
|
base_clip: DisplayObject<'gc>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let name = match swf_function.name {
|
let name = if swf_function.name.is_empty() {
|
||||||
"" => None,
|
None
|
||||||
name => Some(name.to_string()),
|
} else {
|
||||||
|
Some(swf_function.name.to_string_lossy())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut owned_params = Vec::new();
|
let mut owned_params = Vec::new();
|
||||||
|
@ -145,7 +150,7 @@ impl<'gc> Avm1Function<'gc> {
|
||||||
register_index: r,
|
register_index: r,
|
||||||
} in &swf_function.params
|
} in &swf_function.params
|
||||||
{
|
{
|
||||||
owned_params.push((*r, (*s).to_string()))
|
owned_params.push((*r, (*s).to_string_lossy()))
|
||||||
}
|
}
|
||||||
|
|
||||||
Avm1Function {
|
Avm1Function {
|
||||||
|
|
|
@ -841,7 +841,7 @@ pub trait TDisplayObject<'gc>:
|
||||||
self.set_color_transform(gc_context, &color_transform.clone().into());
|
self.set_color_transform(gc_context, &color_transform.clone().into());
|
||||||
}
|
}
|
||||||
if let Some(name) = &place_object.name {
|
if let Some(name) = &place_object.name {
|
||||||
self.set_name(gc_context, name);
|
self.set_name(gc_context, &name.to_str_lossy());
|
||||||
}
|
}
|
||||||
if let Some(clip_depth) = place_object.clip_depth {
|
if let Some(clip_depth) = place_object.clip_depth {
|
||||||
self.set_clip_depth(gc_context, clip_depth.into());
|
self.set_clip_depth(gc_context, clip_depth.into());
|
||||||
|
|
|
@ -156,6 +156,7 @@ impl<'gc> EditText<'gc> {
|
||||||
let mut text_spans = FormatSpans::new();
|
let mut text_spans = FormatSpans::new();
|
||||||
text_spans.set_default_format(default_format.clone());
|
text_spans.set_default_format(default_format.clone());
|
||||||
|
|
||||||
|
let text = text.to_str_lossy();
|
||||||
if is_html {
|
if is_html {
|
||||||
let _ = document
|
let _ = document
|
||||||
.as_node()
|
.as_node()
|
||||||
|
@ -211,13 +212,13 @@ impl<'gc> EditText<'gc> {
|
||||||
id: swf_tag.id,
|
id: swf_tag.id,
|
||||||
bounds: swf_tag.bounds,
|
bounds: swf_tag.bounds,
|
||||||
font_id: swf_tag.font_id,
|
font_id: swf_tag.font_id,
|
||||||
font_class_name: swf_tag.font_class_name.map(str::to_string),
|
font_class_name: swf_tag.font_class_name.map(|s| s.to_string_lossy()),
|
||||||
height: swf_tag.height,
|
height: swf_tag.height,
|
||||||
color: swf_tag.color.clone(),
|
color: swf_tag.color.clone(),
|
||||||
max_length: swf_tag.max_length,
|
max_length: swf_tag.max_length,
|
||||||
layout: swf_tag.layout.clone(),
|
layout: swf_tag.layout.clone(),
|
||||||
variable_name: swf_tag.variable_name.to_string(),
|
variable_name: swf_tag.variable_name.to_string_lossy(),
|
||||||
initial_text: swf_tag.initial_text.map(str::to_string),
|
initial_text: swf_tag.initial_text.map(|s| s.to_string_lossy()),
|
||||||
is_word_wrap: swf_tag.is_word_wrap,
|
is_word_wrap: swf_tag.is_word_wrap,
|
||||||
is_multiline: swf_tag.is_multiline,
|
is_multiline: swf_tag.is_multiline,
|
||||||
is_password: swf_tag.is_password,
|
is_password: swf_tag.is_password,
|
||||||
|
@ -246,7 +247,7 @@ impl<'gc> EditText<'gc> {
|
||||||
intrinsic_bounds,
|
intrinsic_bounds,
|
||||||
bounds,
|
bounds,
|
||||||
autosize: AutoSizeMode::None,
|
autosize: AutoSizeMode::None,
|
||||||
variable: variable.map(str::to_string),
|
variable: variable.map(|s| s.to_string_lossy()),
|
||||||
bound_stage_object: None,
|
bound_stage_object: None,
|
||||||
firing_variable_binding: false,
|
firing_variable_binding: false,
|
||||||
selection: None,
|
selection: None,
|
||||||
|
@ -293,7 +294,7 @@ impl<'gc> EditText<'gc> {
|
||||||
indent: Twips::from_pixels(0.0),
|
indent: Twips::from_pixels(0.0),
|
||||||
leading: Twips::from_pixels(0.0),
|
leading: Twips::from_pixels(0.0),
|
||||||
}),
|
}),
|
||||||
variable_name: "", //TODO: should be null
|
variable_name: "".into(), //TODO: should be null
|
||||||
initial_text: None,
|
initial_text: None,
|
||||||
is_word_wrap: false,
|
is_word_wrap: false,
|
||||||
is_multiline: false,
|
is_multiline: false,
|
||||||
|
|
|
@ -459,7 +459,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
// giving us a `SwfSlice` for later parsing, so we have to replcate the
|
// giving us a `SwfSlice` for later parsing, so we have to replcate the
|
||||||
// *entire* parsing code here. This sucks.
|
// *entire* parsing code here. This sucks.
|
||||||
let flags = reader.read_u32()?;
|
let flags = reader.read_u32()?;
|
||||||
let name = reader.read_c_string()?;
|
let name = reader.read_string()?.to_string_lossy();
|
||||||
let is_lazy_initialize = flags & 1 != 0;
|
let is_lazy_initialize = flags & 1 != 0;
|
||||||
let domain = library.avm2_domain();
|
let domain = library.avm2_domain();
|
||||||
|
|
||||||
|
@ -499,7 +499,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
|
|
||||||
for _ in 0..num_symbols {
|
for _ in 0..num_symbols {
|
||||||
let id = reader.read_u16()?;
|
let id = reader.read_u16()?;
|
||||||
let class_name = reader.read_c_string()?;
|
let class_name = reader.read_string()?.to_string_lossy();
|
||||||
|
|
||||||
if let Some(name) =
|
if let Some(name) =
|
||||||
Avm2QName::from_symbol_class(&class_name, activation.context.gc_context)
|
Avm2QName::from_symbol_class(&class_name, activation.context.gc_context)
|
||||||
|
@ -2458,7 +2458,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
|
||||||
let font = swf::Font {
|
let font = swf::Font {
|
||||||
id: font.id,
|
id: font.id,
|
||||||
version: 0,
|
version: 0,
|
||||||
name: "",
|
name: "".into(),
|
||||||
glyphs,
|
glyphs,
|
||||||
language: swf::Language::Unknown,
|
language: swf::Language::Unknown,
|
||||||
layout: None,
|
layout: None,
|
||||||
|
@ -2609,7 +2609,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
|
||||||
let character = context
|
let character = context
|
||||||
.library
|
.library
|
||||||
.library_for_movie_mut(self.movie())
|
.library_for_movie_mut(self.movie())
|
||||||
.register_export(export.id, &export.name);
|
.register_export(export.id, &export.name.to_str_lossy());
|
||||||
|
|
||||||
// TODO: do other types of Character need to know their exported name?
|
// TODO: do other types of Character need to know their exported name?
|
||||||
if let Some(Character::MovieClip(movie_clip)) = character {
|
if let Some(Character::MovieClip(movie_clip)) = character {
|
||||||
|
@ -2631,7 +2631,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
|
||||||
) -> DecodeResult {
|
) -> DecodeResult {
|
||||||
let frame_label = reader.read_frame_label(tag_len)?;
|
let frame_label = reader.read_frame_label(tag_len)?;
|
||||||
// Frame labels are case insensitive (ASCII).
|
// Frame labels are case insensitive (ASCII).
|
||||||
let label = frame_label.label.to_ascii_lowercase();
|
let label = frame_label.label.to_str_lossy().to_ascii_lowercase();
|
||||||
if let std::collections::hash_map::Entry::Vacant(v) = static_data.frame_labels.entry(label)
|
if let std::collections::hash_map::Entry::Vacant(v) = static_data.frame_labels.entry(label)
|
||||||
{
|
{
|
||||||
v.insert(cur_frame);
|
v.insert(cur_frame);
|
||||||
|
|
|
@ -198,7 +198,7 @@ impl TextFormat {
|
||||||
let font = et.font_id.and_then(|fid| movie_library.get_font(fid));
|
let font = et.font_id.and_then(|fid| movie_library.get_font(fid));
|
||||||
let font_class = et
|
let font_class = et
|
||||||
.font_class_name
|
.font_class_name
|
||||||
.map(str::to_string)
|
.map(|s| s.to_string_lossy())
|
||||||
.or_else(|| font.map(|font| font.descriptor().class().to_string()))
|
.or_else(|| font.map(|font| font.descriptor().class().to_string()))
|
||||||
.unwrap_or_else(|| "Times New Roman".to_string());
|
.unwrap_or_else(|| "Times New Roman".to_string());
|
||||||
let align = et.layout.clone().map(|l| l.align);
|
let align = et.layout.clone().map(|l| l.align);
|
||||||
|
|
|
@ -11,6 +11,7 @@ description = "Read and write the Adobe Flash SWF file format."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.4"
|
byteorder = "1.4"
|
||||||
|
encoding_rs = "0.8.26"
|
||||||
enumset = "1.0.1"
|
enumset = "1.0.1"
|
||||||
num-derive = "0.3"
|
num-derive = "0.3"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
|
|
@ -1,89 +1,97 @@
|
||||||
#![allow(clippy::unreadable_literal)]
|
#![allow(clippy::unreadable_literal)]
|
||||||
|
|
||||||
use crate::avm1::opcode::OpCode;
|
use crate::avm1::{opcode::OpCode, types::*};
|
||||||
use crate::avm1::types::*;
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::read::SwfRead;
|
use crate::read::SwfRead;
|
||||||
use std::io::Cursor;
|
use crate::string::SwfStr;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Reader<'a> {
|
pub struct Reader<'a> {
|
||||||
inner: Cursor<&'a [u8]>,
|
input: &'a [u8],
|
||||||
version: u8,
|
version: u8,
|
||||||
|
encoding: &'static encoding_rs::Encoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SwfRead<Cursor<&'a [u8]>> for Reader<'a> {
|
impl<'a> SwfRead<&'a [u8]> for Reader<'a> {
|
||||||
fn get_inner(&mut self) -> &mut Cursor<&'a [u8]> {
|
fn get_inner(&mut self) -> &mut &'a [u8] {
|
||||||
&mut self.inner
|
&mut self.input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Reader<'a> {
|
impl<'a> Reader<'a> {
|
||||||
pub fn new(input: &'a [u8], version: u8) -> Self {
|
pub fn new(input: &'a [u8], version: u8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Cursor::new(input),
|
input,
|
||||||
version,
|
version,
|
||||||
|
encoding: if version > 5 {
|
||||||
|
encoding_rs::UTF_8
|
||||||
|
} else {
|
||||||
|
// TODO: Allow configurable encoding
|
||||||
|
encoding_rs::WINDOWS_1252
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn get_ref(&self) -> &'a [u8] {
|
||||||
pub fn pos(&self) -> usize {
|
self.input
|
||||||
self.inner.position() as usize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn get_mut(&mut self) -> &mut &'a [u8] {
|
||||||
pub fn seek(&mut self, relative_offset: isize) {
|
&mut self.input
|
||||||
let new_pos = self.inner.position() as i64 + relative_offset as i64;
|
|
||||||
self.inner.set_position(new_pos as u64);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
fn read_slice(&mut self, len: usize) -> io::Result<&'a [u8]> {
|
||||||
fn read_slice(&mut self, len: usize) -> Result<&'a [u8]> {
|
if self.input.len() >= len {
|
||||||
let pos = self.pos();
|
let slice = &self.input[..len];
|
||||||
self.inner.set_position(pos as u64 + len as u64);
|
self.input = &self.input[len..];
|
||||||
let slice = self.inner.get_ref().get(pos..pos + len).ok_or_else(|| {
|
Ok(slice)
|
||||||
std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Buffer underrun")
|
} else {
|
||||||
})?;
|
Err(io::Error::new(
|
||||||
Ok(slice)
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
"Not enough data for slice",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn read_string(&mut self) -> io::Result<SwfStr<'a>> {
|
||||||
fn read_c_string(&mut self) -> Result<&'a str> {
|
let mut pos = 0;
|
||||||
// Find zero terminator.
|
loop {
|
||||||
let str_slice = {
|
let byte = *self.input.get(pos).ok_or(io::Error::new(
|
||||||
let start_pos = self.pos();
|
io::ErrorKind::UnexpectedEof,
|
||||||
loop {
|
"Not enough data for slice",
|
||||||
let byte = self.read_u8()?;
|
))?;
|
||||||
if byte == 0 {
|
if byte == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&self.inner.get_ref()[start_pos..self.pos() - 1]
|
pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = unsafe {
|
||||||
|
let slice = self.input.get_unchecked(..pos);
|
||||||
|
SwfStr::from_bytes_unchecked(slice, self.encoding)
|
||||||
};
|
};
|
||||||
// TODO: What does Flash do on invalid UTF8?
|
self.input = &self.input[pos + 1..];
|
||||||
// Do we silently let it pass?
|
Ok(s)
|
||||||
// TODO: Verify ANSI for SWF 5 and earlier.
|
|
||||||
std::str::from_utf8(str_slice).map_err(|_| Error::invalid_data("Invalid string data"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_action(&mut self) -> Result<Option<Action<'a>>> {
|
pub fn read_action(&mut self) -> Result<Option<Action<'a>>> {
|
||||||
let (opcode, mut length) = self.read_opcode_and_length()?;
|
let (opcode, mut length) = self.read_opcode_and_length()?;
|
||||||
|
let start = self.input.as_ref();
|
||||||
|
|
||||||
let start_pos = self.pos();
|
|
||||||
let action = self.read_op(opcode, &mut length);
|
let action = self.read_op(opcode, &mut length);
|
||||||
|
|
||||||
|
let end_pos = (start.as_ptr() as usize + length) as *const u8;
|
||||||
if let Err(e) = action {
|
if let Err(e) = action {
|
||||||
return Err(Error::avm1_parse_error_with_source(opcode, e));
|
return Err(Error::avm1_parse_error_with_source(opcode, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that we parsed the correct amount of data.
|
// Verify that we parsed the correct amount of data.
|
||||||
let end_pos = start_pos + length;
|
if self.input.as_ptr() != end_pos {
|
||||||
let pos = self.pos();
|
self.input = &start[length.min(start.len())..];
|
||||||
if pos != end_pos {
|
|
||||||
// We incorrectly parsed this action.
|
// We incorrectly parsed this action.
|
||||||
// Re-sync to the expected end of the action and throw an error.
|
// Re-sync to the expected end of the action and throw an error.
|
||||||
use std::convert::TryInto;
|
|
||||||
self.inner.set_position(end_pos.try_into().unwrap());
|
|
||||||
return Err(Error::avm1_parse_error(opcode));
|
return Err(Error::avm1_parse_error(opcode));
|
||||||
}
|
}
|
||||||
action
|
action
|
||||||
|
@ -131,7 +139,7 @@ impl<'a> Reader<'a> {
|
||||||
OpCode::ConstantPool => {
|
OpCode::ConstantPool => {
|
||||||
let mut constants = vec![];
|
let mut constants = vec![];
|
||||||
for _ in 0..self.read_u16()? {
|
for _ in 0..self.read_u16()? {
|
||||||
constants.push(self.read_c_string()?);
|
constants.push(self.read_string()?);
|
||||||
}
|
}
|
||||||
Action::ConstantPool(constants)
|
Action::ConstantPool(constants)
|
||||||
}
|
}
|
||||||
|
@ -153,8 +161,8 @@ impl<'a> Reader<'a> {
|
||||||
OpCode::GetProperty => Action::GetProperty,
|
OpCode::GetProperty => Action::GetProperty,
|
||||||
OpCode::GetTime => Action::GetTime,
|
OpCode::GetTime => Action::GetTime,
|
||||||
OpCode::GetUrl => Action::GetUrl {
|
OpCode::GetUrl => Action::GetUrl {
|
||||||
url: self.read_c_string()?,
|
url: self.read_string()?,
|
||||||
target: self.read_c_string()?,
|
target: self.read_string()?,
|
||||||
},
|
},
|
||||||
OpCode::GetUrl2 => {
|
OpCode::GetUrl2 => {
|
||||||
let flags = self.read_u8()?;
|
let flags = self.read_u8()?;
|
||||||
|
@ -189,7 +197,7 @@ impl<'a> Reader<'a> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OpCode::GotoLabel => Action::GotoLabel(self.read_c_string()?),
|
OpCode::GotoLabel => Action::GotoLabel(self.read_string()?),
|
||||||
OpCode::Greater => Action::Greater,
|
OpCode::Greater => Action::Greater,
|
||||||
OpCode::If => Action::If {
|
OpCode::If => Action::If {
|
||||||
offset: self.read_i16()?,
|
offset: self.read_i16()?,
|
||||||
|
@ -226,7 +234,7 @@ impl<'a> Reader<'a> {
|
||||||
OpCode::Return => Action::Return,
|
OpCode::Return => Action::Return,
|
||||||
OpCode::SetMember => Action::SetMember,
|
OpCode::SetMember => Action::SetMember,
|
||||||
OpCode::SetProperty => Action::SetProperty,
|
OpCode::SetProperty => Action::SetProperty,
|
||||||
OpCode::SetTarget => Action::SetTarget(self.read_c_string()?),
|
OpCode::SetTarget => Action::SetTarget(self.read_string()?),
|
||||||
OpCode::SetTarget2 => Action::SetTarget2,
|
OpCode::SetTarget2 => Action::SetTarget2,
|
||||||
OpCode::SetVariable => Action::SetVariable,
|
OpCode::SetVariable => Action::SetVariable,
|
||||||
OpCode::StackSwap => Action::StackSwap,
|
OpCode::StackSwap => Action::StackSwap,
|
||||||
|
@ -281,9 +289,9 @@ impl<'a> Reader<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_push(&mut self, length: usize) -> Result<Action<'a>> {
|
fn read_push(&mut self, length: usize) -> Result<Action<'a>> {
|
||||||
let end_pos = self.pos() + length;
|
let end_pos = (self.input.as_ptr() as usize + length) as *const u8;
|
||||||
let mut values = Vec::with_capacity(4);
|
let mut values = Vec::with_capacity(4);
|
||||||
while self.pos() < end_pos {
|
while self.input.as_ptr() < end_pos {
|
||||||
values.push(self.read_push_value()?);
|
values.push(self.read_push_value()?);
|
||||||
}
|
}
|
||||||
Ok(Action::Push(values))
|
Ok(Action::Push(values))
|
||||||
|
@ -291,7 +299,7 @@ impl<'a> Reader<'a> {
|
||||||
|
|
||||||
fn read_push_value(&mut self) -> Result<Value<'a>> {
|
fn read_push_value(&mut self) -> Result<Value<'a>> {
|
||||||
let value = match self.read_u8()? {
|
let value = match self.read_u8()? {
|
||||||
0 => Value::Str(self.read_c_string()?),
|
0 => Value::Str(self.read_string()?),
|
||||||
1 => Value::Float(self.read_f32()?),
|
1 => Value::Float(self.read_f32()?),
|
||||||
2 => Value::Null,
|
2 => Value::Null,
|
||||||
3 => Value::Undefined,
|
3 => Value::Undefined,
|
||||||
|
@ -307,11 +315,11 @@ impl<'a> Reader<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_define_function(&mut self, action_length: &mut usize) -> Result<Action<'a>> {
|
fn read_define_function(&mut self, action_length: &mut usize) -> Result<Action<'a>> {
|
||||||
let name = self.read_c_string()?;
|
let name = self.read_string()?;
|
||||||
let num_params = self.read_u16()?;
|
let num_params = self.read_u16()?;
|
||||||
let mut params = Vec::with_capacity(num_params as usize);
|
let mut params = Vec::with_capacity(num_params as usize);
|
||||||
for _ in 0..num_params {
|
for _ in 0..num_params {
|
||||||
params.push(self.read_c_string()?);
|
params.push(self.read_string()?);
|
||||||
}
|
}
|
||||||
// code_length isn't included in the DefineFunction's action length.
|
// code_length isn't included in the DefineFunction's action length.
|
||||||
let code_length = usize::from(self.read_u16()?);
|
let code_length = usize::from(self.read_u16()?);
|
||||||
|
@ -324,7 +332,7 @@ impl<'a> Reader<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_define_function_2(&mut self, action_length: &mut usize) -> Result<Action<'a>> {
|
fn read_define_function_2(&mut self, action_length: &mut usize) -> Result<Action<'a>> {
|
||||||
let name = self.read_c_string()?;
|
let name = self.read_string()?;
|
||||||
let num_params = self.read_u16()?;
|
let num_params = self.read_u16()?;
|
||||||
let register_count = self.read_u8()?; // Number of registers
|
let register_count = self.read_u8()?; // Number of registers
|
||||||
let flags = self.read_u16()?;
|
let flags = self.read_u16()?;
|
||||||
|
@ -332,7 +340,7 @@ impl<'a> Reader<'a> {
|
||||||
for _ in 0..num_params {
|
for _ in 0..num_params {
|
||||||
let register = self.read_u8()?;
|
let register = self.read_u8()?;
|
||||||
params.push(FunctionParam {
|
params.push(FunctionParam {
|
||||||
name: self.read_c_string()?,
|
name: self.read_string()?,
|
||||||
register_index: if register == 0 { None } else { Some(register) },
|
register_index: if register == 0 { None } else { Some(register) },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -363,7 +371,7 @@ impl<'a> Reader<'a> {
|
||||||
let finally_length = usize::from(self.read_u16()?);
|
let finally_length = usize::from(self.read_u16()?);
|
||||||
*length += try_length + catch_length + finally_length;
|
*length += try_length + catch_length + finally_length;
|
||||||
let catch_var = if flags & 0b100 == 0 {
|
let catch_var = if flags & 0b100 == 0 {
|
||||||
CatchVar::Var(self.read_c_string()?)
|
CatchVar::Var(self.read_string()?)
|
||||||
} else {
|
} else {
|
||||||
CatchVar::Register(self.read_u8()?)
|
CatchVar::Register(self.read_u8()?)
|
||||||
};
|
};
|
||||||
|
@ -421,6 +429,7 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_define_function() {
|
fn read_define_function() {
|
||||||
|
use encoding_rs::WINDOWS_1252;
|
||||||
// Ensure we read a function properly along with the function data.
|
// Ensure we read a function properly along with the function data.
|
||||||
let action_bytes = vec![
|
let action_bytes = vec![
|
||||||
0x9b, 0x08, 0x00, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x96, 0x06, 0x00,
|
0x9b, 0x08, 0x00, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x96, 0x06, 0x00,
|
||||||
|
@ -431,7 +440,7 @@ pub mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
action,
|
action,
|
||||||
Action::DefineFunction {
|
Action::DefineFunction {
|
||||||
name: "foo",
|
name: SwfStr::from_str_with_encoding("foo", WINDOWS_1252).unwrap(),
|
||||||
params: vec![],
|
params: vec![],
|
||||||
actions: &[0x96, 0x06, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00, 0x26],
|
actions: &[0x96, 0x06, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00, 0x26],
|
||||||
}
|
}
|
||||||
|
@ -440,7 +449,12 @@ pub mod tests {
|
||||||
if let Action::DefineFunction { actions, .. } = action {
|
if let Action::DefineFunction { actions, .. } = action {
|
||||||
let mut reader = Reader::new(actions, 5);
|
let mut reader = Reader::new(actions, 5);
|
||||||
let action = reader.read_action().unwrap().unwrap();
|
let action = reader.read_action().unwrap().unwrap();
|
||||||
assert_eq!(action, Action::Push(vec![Value::Str("test")]));
|
assert_eq!(
|
||||||
|
action,
|
||||||
|
Action::Push(vec![Value::Str(
|
||||||
|
SwfStr::from_str_with_encoding("test", WINDOWS_1252).unwrap()
|
||||||
|
)])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::string::SwfStr;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Action<'a> {
|
pub enum Action<'a> {
|
||||||
Add,
|
Add,
|
||||||
|
@ -16,11 +18,11 @@ pub enum Action<'a> {
|
||||||
CastOp,
|
CastOp,
|
||||||
CharToAscii,
|
CharToAscii,
|
||||||
CloneSprite,
|
CloneSprite,
|
||||||
ConstantPool(Vec<&'a str>),
|
ConstantPool(Vec<SwfStr<'a>>),
|
||||||
Decrement,
|
Decrement,
|
||||||
DefineFunction {
|
DefineFunction {
|
||||||
name: &'a str,
|
name: SwfStr<'a>,
|
||||||
params: Vec<&'a str>,
|
params: Vec<SwfStr<'a>>,
|
||||||
actions: &'a [u8],
|
actions: &'a [u8],
|
||||||
},
|
},
|
||||||
DefineFunction2(Function<'a>),
|
DefineFunction2(Function<'a>),
|
||||||
|
@ -39,8 +41,8 @@ pub enum Action<'a> {
|
||||||
GetProperty,
|
GetProperty,
|
||||||
GetTime,
|
GetTime,
|
||||||
GetUrl {
|
GetUrl {
|
||||||
url: &'a str,
|
url: SwfStr<'a>,
|
||||||
target: &'a str,
|
target: SwfStr<'a>,
|
||||||
},
|
},
|
||||||
GetUrl2 {
|
GetUrl2 {
|
||||||
send_vars_method: SendVarsMethod,
|
send_vars_method: SendVarsMethod,
|
||||||
|
@ -53,7 +55,7 @@ pub enum Action<'a> {
|
||||||
set_playing: bool,
|
set_playing: bool,
|
||||||
scene_offset: u16,
|
scene_offset: u16,
|
||||||
},
|
},
|
||||||
GotoLabel(&'a str),
|
GotoLabel(SwfStr<'a>),
|
||||||
Greater,
|
Greater,
|
||||||
If {
|
If {
|
||||||
offset: i16,
|
offset: i16,
|
||||||
|
@ -89,7 +91,7 @@ pub enum Action<'a> {
|
||||||
Return,
|
Return,
|
||||||
SetMember,
|
SetMember,
|
||||||
SetProperty,
|
SetProperty,
|
||||||
SetTarget(&'a str),
|
SetTarget(SwfStr<'a>),
|
||||||
SetTarget2,
|
SetTarget2,
|
||||||
SetVariable,
|
SetVariable,
|
||||||
StackSwap,
|
StackSwap,
|
||||||
|
@ -138,7 +140,7 @@ pub enum Value<'a> {
|
||||||
Int(i32),
|
Int(i32),
|
||||||
Float(f32),
|
Float(f32),
|
||||||
Double(f64),
|
Double(f64),
|
||||||
Str(&'a str),
|
Str(SwfStr<'a>),
|
||||||
Register(u8),
|
Register(u8),
|
||||||
ConstantPool(u16),
|
ConstantPool(u16),
|
||||||
}
|
}
|
||||||
|
@ -152,7 +154,7 @@ pub enum SendVarsMethod {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Function<'a> {
|
pub struct Function<'a> {
|
||||||
pub name: &'a str,
|
pub name: SwfStr<'a>,
|
||||||
pub register_count: u8,
|
pub register_count: u8,
|
||||||
pub params: Vec<FunctionParam<'a>>,
|
pub params: Vec<FunctionParam<'a>>,
|
||||||
pub preload_parent: bool,
|
pub preload_parent: bool,
|
||||||
|
@ -169,7 +171,7 @@ pub struct Function<'a> {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct FunctionParam<'a> {
|
pub struct FunctionParam<'a> {
|
||||||
pub name: &'a str,
|
pub name: SwfStr<'a>,
|
||||||
pub register_index: Option<u8>,
|
pub register_index: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,6 +184,6 @@ pub struct TryBlock<'a> {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum CatchVar<'a> {
|
pub enum CatchVar<'a> {
|
||||||
Var(&'a str),
|
Var(SwfStr<'a>),
|
||||||
Register(u8),
|
Register(u8),
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ impl<W: Write> Writer<W> {
|
||||||
self.write_action_header(OpCode::ConstantPool, len)?;
|
self.write_action_header(OpCode::ConstantPool, len)?;
|
||||||
self.write_u16(constants.len() as u16)?;
|
self.write_u16(constants.len() as u16)?;
|
||||||
for constant in constants {
|
for constant in constants {
|
||||||
self.write_c_string(constant)?;
|
self.write_string(*constant)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::Decrement => self.write_action_header(OpCode::Decrement, 0)?,
|
Action::Decrement => self.write_action_header(OpCode::Decrement, 0)?,
|
||||||
|
@ -60,10 +60,10 @@ impl<W: Write> Writer<W> {
|
||||||
let len =
|
let len =
|
||||||
name.len() + 1 + 2 + params.iter().map(|p| p.len() + 1).sum::<usize>() + 2;
|
name.len() + 1 + 2 + params.iter().map(|p| p.len() + 1).sum::<usize>() + 2;
|
||||||
self.write_action_header(OpCode::DefineFunction, len)?;
|
self.write_action_header(OpCode::DefineFunction, len)?;
|
||||||
self.write_c_string(name)?;
|
self.write_string(*name)?;
|
||||||
self.write_u16(params.len() as u16)?;
|
self.write_u16(params.len() as u16)?;
|
||||||
for param in params {
|
for param in params {
|
||||||
self.write_c_string(param)?;
|
self.write_string(*param)?;
|
||||||
}
|
}
|
||||||
self.write_u16(actions.len() as u16)?;
|
self.write_u16(actions.len() as u16)?;
|
||||||
self.inner.write_all(actions)?;
|
self.inner.write_all(actions)?;
|
||||||
|
@ -79,7 +79,7 @@ impl<W: Write> Writer<W> {
|
||||||
.sum::<usize>()
|
.sum::<usize>()
|
||||||
+ 4;
|
+ 4;
|
||||||
self.write_action_header(OpCode::DefineFunction2, len)?;
|
self.write_action_header(OpCode::DefineFunction2, len)?;
|
||||||
self.write_c_string(&function.name)?;
|
self.write_string(function.name)?;
|
||||||
self.write_u16(function.params.len() as u16)?;
|
self.write_u16(function.params.len() as u16)?;
|
||||||
self.write_u8(function.register_count)?;
|
self.write_u8(function.register_count)?;
|
||||||
let flags = if function.preload_global {
|
let flags = if function.preload_global {
|
||||||
|
@ -108,7 +108,7 @@ impl<W: Write> Writer<W> {
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
})?;
|
})?;
|
||||||
self.write_c_string(¶m.name)?;
|
self.write_string(param.name)?;
|
||||||
}
|
}
|
||||||
self.write_u16(function.actions.len() as u16)?;
|
self.write_u16(function.actions.len() as u16)?;
|
||||||
self.inner.write_all(&function.actions)?;
|
self.inner.write_all(&function.actions)?;
|
||||||
|
@ -132,8 +132,8 @@ impl<W: Write> Writer<W> {
|
||||||
ref target,
|
ref target,
|
||||||
} => {
|
} => {
|
||||||
self.write_action_header(OpCode::GetUrl, url.len() + target.len() + 2)?;
|
self.write_action_header(OpCode::GetUrl, url.len() + target.len() + 2)?;
|
||||||
self.write_c_string(url)?;
|
self.write_string(*url)?;
|
||||||
self.write_c_string(target)?;
|
self.write_string(*target)?;
|
||||||
}
|
}
|
||||||
Action::GetUrl2 {
|
Action::GetUrl2 {
|
||||||
send_vars_method,
|
send_vars_method,
|
||||||
|
@ -169,7 +169,7 @@ impl<W: Write> Writer<W> {
|
||||||
}
|
}
|
||||||
Action::GotoLabel(ref label) => {
|
Action::GotoLabel(ref label) => {
|
||||||
self.write_action_header(OpCode::GotoLabel, label.len() + 1)?;
|
self.write_action_header(OpCode::GotoLabel, label.len() + 1)?;
|
||||||
self.write_c_string(label)?;
|
self.write_string(*label)?;
|
||||||
}
|
}
|
||||||
Action::Greater => self.write_action_header(OpCode::Greater, 0)?,
|
Action::Greater => self.write_action_header(OpCode::Greater, 0)?,
|
||||||
Action::If { offset } => {
|
Action::If { offset } => {
|
||||||
|
@ -232,7 +232,7 @@ impl<W: Write> Writer<W> {
|
||||||
Action::SetProperty => self.write_action_header(OpCode::SetProperty, 0)?,
|
Action::SetProperty => self.write_action_header(OpCode::SetProperty, 0)?,
|
||||||
Action::SetTarget(ref target) => {
|
Action::SetTarget(ref target) => {
|
||||||
self.write_action_header(OpCode::SetTarget, target.len() + 1)?;
|
self.write_action_header(OpCode::SetTarget, target.len() + 1)?;
|
||||||
self.write_c_string(target)?;
|
self.write_string(*target)?;
|
||||||
}
|
}
|
||||||
Action::SetTarget2 => self.write_action_header(OpCode::SetTarget2, 0)?,
|
Action::SetTarget2 => self.write_action_header(OpCode::SetTarget2, 0)?,
|
||||||
Action::SetVariable => self.write_action_header(OpCode::SetVariable, 0)?,
|
Action::SetVariable => self.write_action_header(OpCode::SetVariable, 0)?,
|
||||||
|
@ -300,7 +300,7 @@ impl<W: Write> Writer<W> {
|
||||||
self.write_u16(catch_length as u16)?;
|
self.write_u16(catch_length as u16)?;
|
||||||
self.write_u16(finally_length as u16)?;
|
self.write_u16(finally_length as u16)?;
|
||||||
match try_block.catch {
|
match try_block.catch {
|
||||||
Some((CatchVar::Var(ref name), _)) => self.write_c_string(name)?,
|
Some((CatchVar::Var(name), _)) => self.write_string(name)?,
|
||||||
Some((CatchVar::Register(i), _)) => self.write_u8(i)?,
|
Some((CatchVar::Register(i), _)) => self.write_u8(i)?,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -352,9 +352,9 @@ impl<W: Write> Writer<W> {
|
||||||
|
|
||||||
fn write_push_value(&mut self, value: &Value) -> Result<()> {
|
fn write_push_value(&mut self, value: &Value) -> Result<()> {
|
||||||
match *value {
|
match *value {
|
||||||
Value::Str(ref string) => {
|
Value::Str(string) => {
|
||||||
self.write_u8(0)?;
|
self.write_u8(0)?;
|
||||||
self.write_c_string(string)?;
|
self.write_string(string)?;
|
||||||
}
|
}
|
||||||
Value::Float(v) => {
|
Value::Float(v) => {
|
||||||
self.write_u8(1)?;
|
self.write_u8(1)?;
|
||||||
|
|
|
@ -21,6 +21,7 @@ pub mod avm1;
|
||||||
pub mod avm2;
|
pub mod avm2;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod read;
|
pub mod read;
|
||||||
|
mod string;
|
||||||
mod tag_code;
|
mod tag_code;
|
||||||
mod types;
|
mod types;
|
||||||
pub mod write;
|
pub mod write;
|
||||||
|
@ -30,6 +31,7 @@ mod test_data;
|
||||||
|
|
||||||
/// Reexports
|
/// Reexports
|
||||||
pub use read::{read_swf, read_swf_header};
|
pub use read::{read_swf, read_swf_header};
|
||||||
|
pub use string::*;
|
||||||
pub use tag_code::TagCode;
|
pub use tag_code::TagCode;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
pub use write::write_swf;
|
pub use write::write_swf;
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
clippy::unreadable_literal
|
clippy::unreadable_literal
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::{
|
||||||
use crate::types::*;
|
error::{Error, Result},
|
||||||
|
string::SwfStr,
|
||||||
|
types::*,
|
||||||
|
};
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
@ -246,20 +249,6 @@ pub trait SwfRead<R: Read> {
|
||||||
num.swap(3, 7);
|
num.swap(3, 7);
|
||||||
(&num[..]).read_f64::<LittleEndian>()
|
(&num[..]).read_f64::<LittleEndian>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_c_string(&mut self) -> Result<String> {
|
|
||||||
let mut bytes = Vec::new();
|
|
||||||
loop {
|
|
||||||
let byte = self.read_u8()?;
|
|
||||||
if byte == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
bytes.push(byte)
|
|
||||||
}
|
|
||||||
// TODO: There is probably a better way to do this.
|
|
||||||
// TODO: Verify ANSI for SWF 5 and earlier.
|
|
||||||
String::from_utf8(bytes).map_err(|_| Error::invalid_data("Invalid string data"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BitReader<'a, 'b> {
|
pub struct BitReader<'a, 'b> {
|
||||||
|
@ -324,6 +313,7 @@ impl<'a, 'b> BitReader<'a, 'b> {
|
||||||
pub struct Reader<'a> {
|
pub struct Reader<'a> {
|
||||||
input: &'a [u8],
|
input: &'a [u8],
|
||||||
version: u8,
|
version: u8,
|
||||||
|
encoding: &'static encoding_rs::Encoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SwfRead<&'a [u8]> for Reader<'a> {
|
impl<'a> SwfRead<&'a [u8]> for Reader<'a> {
|
||||||
|
@ -366,7 +356,16 @@ impl<'a> SwfRead<&'a [u8]> for Reader<'a> {
|
||||||
|
|
||||||
impl<'a> Reader<'a> {
|
impl<'a> Reader<'a> {
|
||||||
pub fn new(input: &'a [u8], version: u8) -> Reader<'a> {
|
pub fn new(input: &'a [u8], version: u8) -> Reader<'a> {
|
||||||
Reader { input, version }
|
Reader {
|
||||||
|
input,
|
||||||
|
version,
|
||||||
|
encoding: if version > 5 {
|
||||||
|
encoding_rs::UTF_8
|
||||||
|
} else {
|
||||||
|
// TODO: Allow configurable encoding
|
||||||
|
encoding_rs::WINDOWS_1252
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(&self) -> u8 {
|
pub fn version(&self) -> u8 {
|
||||||
|
@ -378,6 +377,13 @@ impl<'a> Reader<'a> {
|
||||||
self.input
|
self.input
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the underlying `Reader`.
|
||||||
|
///
|
||||||
|
/// Reading from this reference is not recommended.
|
||||||
|
pub fn get_mut(&mut self) -> &mut &'a [u8] {
|
||||||
|
&mut self.input
|
||||||
|
}
|
||||||
|
|
||||||
fn bits<'b>(&'b mut self) -> BitReader<'a, 'b> {
|
fn bits<'b>(&'b mut self) -> BitReader<'a, 'b> {
|
||||||
BitReader {
|
BitReader {
|
||||||
input: self.get_inner(),
|
input: self.get_inner(),
|
||||||
|
@ -406,7 +412,7 @@ impl<'a> Reader<'a> {
|
||||||
slice
|
slice
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_string(&mut self) -> io::Result<SwfStr<'a>> {
|
pub fn read_string(&mut self) -> io::Result<SwfStr<'a>> {
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
loop {
|
loop {
|
||||||
let byte = *self.input.get(pos).ok_or(io::Error::new(
|
let byte = *self.input.get(pos).ok_or(io::Error::new(
|
||||||
|
@ -418,26 +424,17 @@ impl<'a> Reader<'a> {
|
||||||
}
|
}
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
// TODO: There is probably a better way to do this.
|
|
||||||
// TODO: Verify ANSI for SWF 5 and earlier.
|
let s = unsafe {
|
||||||
let s = unsafe { std::str::from_utf8_unchecked(&self.input.get_unchecked(..pos)) };
|
let slice = self.input.get_unchecked(..pos);
|
||||||
|
SwfStr::from_bytes_unchecked(slice, self.encoding)
|
||||||
|
};
|
||||||
self.input = &self.input[pos + 1..];
|
self.input = &self.input[pos + 1..];
|
||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_string_with_len(&mut self, len: usize) -> io::Result<SwfStr<'a>> {
|
fn read_string_with_len(&mut self, len: usize) -> io::Result<SwfStr<'a>> {
|
||||||
// TODO: There is probably a better way to do this.
|
Ok(SwfStr::from_bytes(&self.read_slice(len)?, self.encoding))
|
||||||
// TODO: Verify ANSI for SWF 5 and earlier.
|
|
||||||
let mut s = unsafe { std::str::from_utf8_unchecked(&self.read_slice(len)?) };
|
|
||||||
s = s.trim_end_matches('\0');
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable reference to the underlying `Reader`.
|
|
||||||
///
|
|
||||||
/// Reading from this reference is not recommended.
|
|
||||||
pub fn get_mut(&mut self) -> &mut &'a [u8] {
|
|
||||||
&mut self.input
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the next SWF tag from the stream.
|
/// Reads the next SWF tag from the stream.
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
//! String typed used by SWF files.
|
||||||
|
//!
|
||||||
|
//! Allows for locale-dependent encoding for SWF version <6.
|
||||||
|
|
||||||
|
use encoding_rs::{Encoding, UTF_8};
|
||||||
|
use std::{borrow::Cow, fmt};
|
||||||
|
|
||||||
|
/// `SwfStr` is returned by SWF and AVM1 parsing functions.
|
||||||
|
/// `SwfStr` is analogous to `&str`, with some additional allowances:
|
||||||
|
/// * An encoding is specified along with the string data.
|
||||||
|
/// * The string contains no null bytes.
|
||||||
|
/// * Invalid data for any particular encoding is allowed;
|
||||||
|
/// any conversions to std::String will be lossy for invalid data.
|
||||||
|
/// This handles the locale dependent encoding of early SWF files and
|
||||||
|
/// mimics C-style null-terminated string behavior.
|
||||||
|
/// To convert this to a standard Rust string, use `SwfStr::to_str_lossy`.
|
||||||
|
/// `SwfStr`s are equal if both their encoding and data matches.
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct SwfStr<'a> {
|
||||||
|
/// The string bytes.
|
||||||
|
string: &'a [u8],
|
||||||
|
|
||||||
|
/// The encoding of the string data.
|
||||||
|
encoding: &'static Encoding,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SwfStr<'a> {
|
||||||
|
/// Create a new `SwfStr` from a byte slice with a given encoding.
|
||||||
|
/// The string will be truncated if a null byte is encountered.
|
||||||
|
/// The data is not required to be valid for the given encoding.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_bytes(string: &'a [u8], encoding: &'static Encoding) -> Self {
|
||||||
|
let i = string
|
||||||
|
.into_iter()
|
||||||
|
.position(|&c| c == 0)
|
||||||
|
.unwrap_or(string.len());
|
||||||
|
Self {
|
||||||
|
string: &string[..i],
|
||||||
|
encoding,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `SwfStr` from a byte slice with a given encoding.
|
||||||
|
/// The string should contain no null bytes, but ths is not checked.
|
||||||
|
/// The data is not required to be valid for the given encoding.
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn from_bytes_unchecked(string: &'a [u8], encoding: &'static Encoding) -> Self {
|
||||||
|
Self { string, encoding }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new UTF-8 `SwfStr` from a Rust `str`.
|
||||||
|
/// The string will be truncated if a null byte is encountered.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_str(string: &'a str) -> Self {
|
||||||
|
Self::from_bytes(string.as_bytes(), UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `SwfStr` with the given encoding from a Rust `str`.
|
||||||
|
/// The string will be re-encoded with the given encoding.
|
||||||
|
/// The string will be truncated if a null byte is encountered.
|
||||||
|
/// `None` is returned if the encoding is not lossless.
|
||||||
|
/// Intended for tests.
|
||||||
|
pub fn from_str_with_encoding(string: &'a str, encoding: &'static Encoding) -> Option<Self> {
|
||||||
|
if let (Cow::Borrowed(s), _, false) = encoding.encode(&string) {
|
||||||
|
Some(Self::from_bytes(s, encoding))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the byte slice of this string.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_bytes(&self) -> &'a [u8] {
|
||||||
|
self.string
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the encoding used by this string.
|
||||||
|
#[inline]
|
||||||
|
pub fn encoding(&self) -> &'static Encoding {
|
||||||
|
self.encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the string has a length of zero, and `false` otherwise.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.string.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `len` of the string in bytes.
|
||||||
|
#[inline]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.string.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes the string into a Rust UTF-8 `str`.
|
||||||
|
/// The UTF-8 replacement character will be uses for any invalid data.
|
||||||
|
#[inline]
|
||||||
|
pub fn to_str_lossy(&self) -> Cow<'a, str> {
|
||||||
|
self.encoding.decode_without_bom_handling(self.string).0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes the string into a Rust UTF-8 `String`.
|
||||||
|
/// The UTF-8 replacement character will be uses for any invalid data.
|
||||||
|
#[inline]
|
||||||
|
pub fn to_string_lossy(&self) -> String {
|
||||||
|
self.to_str_lossy().into_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Default for SwfStr<'a> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
string: &[],
|
||||||
|
encoding: UTF_8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for SwfStr<'a> {
|
||||||
|
fn from(s: &'a str) -> Self {
|
||||||
|
SwfStr::from_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: AsRef<str>> PartialEq<T> for SwfStr<'a> {
|
||||||
|
fn eq(&self, other: &T) -> bool {
|
||||||
|
self.string == other.as_ref().as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> fmt::Display for SwfStr<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.to_str_lossy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> fmt::Debug for SwfStr<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.to_str_lossy())
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,11 +3,13 @@
|
||||||
use crate::avm1::types::*;
|
use crate::avm1::types::*;
|
||||||
use crate::avm2::read::tests::read_abc_from_file;
|
use crate::avm2::read::tests::read_abc_from_file;
|
||||||
use crate::avm2::types::*;
|
use crate::avm2::types::*;
|
||||||
use crate::read::{read_swf, read_swf_header};
|
|
||||||
use crate::read::tests::{read_tag_bytes_from_file, read_tag_bytes_from_file_with_index};
|
use crate::read::tests::{read_tag_bytes_from_file, read_tag_bytes_from_file_with_index};
|
||||||
|
use crate::read::{read_swf, read_swf_header};
|
||||||
|
use crate::string::SwfStr;
|
||||||
use crate::tag_code::TagCode;
|
use crate::tag_code::TagCode;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
use crate::write::write_swf;
|
use crate::write::write_swf;
|
||||||
|
use encoding_rs::WINDOWS_1252;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
@ -292,7 +294,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
},
|
},
|
||||||
ButtonAction {
|
ButtonAction {
|
||||||
conditions: vec![ButtonActionCondition::KeyPress].into_iter().collect(),
|
conditions: vec![ButtonActionCondition::KeyPress].into_iter().collect(),
|
||||||
key_code: Some(3), // Home
|
key_code: Some(3), // Home
|
||||||
action_data: &[150, 3, 0, 0, 66, 0, 38, 0], // trace("B");
|
action_data: &[150, 3, 0, 0, 66, 0, 38, 0], // trace("B");
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -400,8 +402,8 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
indent: Twips::from_pixels(1.0),
|
indent: Twips::from_pixels(1.0),
|
||||||
leading: Twips::from_pixels(2.0),
|
leading: Twips::from_pixels(2.0),
|
||||||
}),
|
}),
|
||||||
variable_name: "foo",
|
variable_name: SwfStr::from_str_with_encoding("foo", WINDOWS_1252).unwrap(),
|
||||||
initial_text: Some("-_-"),
|
initial_text: Some(SwfStr::from_str_with_encoding("-_-", WINDOWS_1252).unwrap()),
|
||||||
is_word_wrap: false,
|
is_word_wrap: false,
|
||||||
is_multiline: true,
|
is_multiline: true,
|
||||||
is_password: false,
|
is_password: false,
|
||||||
|
@ -670,7 +672,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
10,
|
10,
|
||||||
Tag::DefineFont4(Font4 {
|
Tag::DefineFont4(Font4 {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "Dummy",
|
name: "Dummy".into(),
|
||||||
is_italic: false,
|
is_italic: false,
|
||||||
is_bold: false,
|
is_bold: false,
|
||||||
data: None,
|
data: None,
|
||||||
|
@ -682,7 +684,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
Tag::DefineFontInfo(Box::new(FontInfo {
|
Tag::DefineFontInfo(Box::new(FontInfo {
|
||||||
id: 1,
|
id: 1,
|
||||||
version: 1,
|
version: 1,
|
||||||
name: "Verdana",
|
name: SwfStr::from_str_with_encoding("Verdana", WINDOWS_1252).unwrap(),
|
||||||
is_small_text: false,
|
is_small_text: false,
|
||||||
is_ansi: true,
|
is_ansi: true,
|
||||||
is_shift_jis: false,
|
is_shift_jis: false,
|
||||||
|
@ -698,7 +700,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
Tag::DefineFontInfo(Box::new(FontInfo {
|
Tag::DefineFontInfo(Box::new(FontInfo {
|
||||||
id: 1,
|
id: 1,
|
||||||
version: 2,
|
version: 2,
|
||||||
name: "Verdana",
|
name: "Verdana".into(),
|
||||||
is_small_text: false,
|
is_small_text: false,
|
||||||
is_ansi: true,
|
is_ansi: true,
|
||||||
is_shift_jis: false,
|
is_shift_jis: false,
|
||||||
|
@ -713,8 +715,8 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
9,
|
9,
|
||||||
Tag::DefineFontName {
|
Tag::DefineFontName {
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Dummy",
|
name: "Dummy".into(),
|
||||||
copyright_info: "Dummy font for swf-rs tests",
|
copyright_info: "Dummy font for swf-rs tests".into(),
|
||||||
},
|
},
|
||||||
read_tag_bytes_from_file("tests/swfs/DefineFont4.swf", TagCode::DefineFontName),
|
read_tag_bytes_from_file("tests/swfs/DefineFont4.swf", TagCode::DefineFontName),
|
||||||
),
|
),
|
||||||
|
@ -1397,38 +1399,38 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
1, // Minimum version not listed in SWF19.
|
9, // Minimum version not listed in SWF19.
|
||||||
Tag::DefineSceneAndFrameLabelData(DefineSceneAndFrameLabelData {
|
Tag::DefineSceneAndFrameLabelData(DefineSceneAndFrameLabelData {
|
||||||
scenes: vec![
|
scenes: vec![
|
||||||
FrameLabelData {
|
FrameLabelData {
|
||||||
frame_num: 0,
|
frame_num: 0,
|
||||||
label: "Scene 1",
|
label: "Scene 1".into(),
|
||||||
},
|
},
|
||||||
FrameLabelData {
|
FrameLabelData {
|
||||||
frame_num: 25,
|
frame_num: 25,
|
||||||
label: "Scene2Scene2Scene2Scene2Scene2",
|
label: "Scene2Scene2Scene2Scene2Scene2".into(),
|
||||||
},
|
},
|
||||||
FrameLabelData {
|
FrameLabelData {
|
||||||
frame_num: 26,
|
frame_num: 26,
|
||||||
label: "test日本語test",
|
label: "test日本語test".into(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
frame_labels: vec![
|
frame_labels: vec![
|
||||||
FrameLabelData {
|
FrameLabelData {
|
||||||
frame_num: 0,
|
frame_num: 0,
|
||||||
label: "a",
|
label: "a".into(),
|
||||||
},
|
},
|
||||||
FrameLabelData {
|
FrameLabelData {
|
||||||
frame_num: 9,
|
frame_num: 9,
|
||||||
label: "b",
|
label: "b".into(),
|
||||||
},
|
},
|
||||||
FrameLabelData {
|
FrameLabelData {
|
||||||
frame_num: 17,
|
frame_num: 17,
|
||||||
label: "❤😁aaa",
|
label: "❤😁aaa".into(),
|
||||||
},
|
},
|
||||||
FrameLabelData {
|
FrameLabelData {
|
||||||
frame_num: 25,
|
frame_num: 25,
|
||||||
label: "frameInScene2",
|
label: "frameInScene2".into(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
@ -1954,7 +1956,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
6,
|
6,
|
||||||
Tag::EnableDebugger("$1$ve$EG3LE6bumvJ2pR8F5qXny/"),
|
Tag::EnableDebugger("$1$ve$EG3LE6bumvJ2pR8F5qXny/".into()),
|
||||||
read_tag_bytes_from_file(
|
read_tag_bytes_from_file(
|
||||||
"tests/swfs/EnableDebugger2-CS6.swf",
|
"tests/swfs/EnableDebugger2-CS6.swf",
|
||||||
TagCode::EnableDebugger2,
|
TagCode::EnableDebugger2,
|
||||||
|
@ -1962,9 +1964,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
10,
|
10,
|
||||||
Tag::EnableTelemetry {
|
Tag::EnableTelemetry { password_hash: &[] },
|
||||||
password_hash: &[],
|
|
||||||
},
|
|
||||||
read_tag_bytes_from_file("tests/swfs/EnableTelemetry.swf", TagCode::EnableTelemetry),
|
read_tag_bytes_from_file("tests/swfs/EnableTelemetry.swf", TagCode::EnableTelemetry),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -1984,7 +1984,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
6,
|
6,
|
||||||
Tag::ExportAssets(vec![ExportedAsset {
|
Tag::ExportAssets(vec![ExportedAsset {
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Test💯",
|
name: "Test💯".into(),
|
||||||
}]),
|
}]),
|
||||||
read_tag_bytes_from_file("tests/swfs/ExportAssets-CS6.swf", TagCode::ExportAssets),
|
read_tag_bytes_from_file("tests/swfs/ExportAssets-CS6.swf", TagCode::ExportAssets),
|
||||||
),
|
),
|
||||||
|
@ -2002,7 +2002,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
(
|
(
|
||||||
3,
|
3,
|
||||||
Tag::FrameLabel(FrameLabel {
|
Tag::FrameLabel(FrameLabel {
|
||||||
label: "test",
|
label: SwfStr::from_str_with_encoding("test", WINDOWS_1252).unwrap(),
|
||||||
is_anchor: false,
|
is_anchor: false,
|
||||||
}),
|
}),
|
||||||
read_tag_bytes_from_file_with_index(
|
read_tag_bytes_from_file_with_index(
|
||||||
|
@ -2014,7 +2014,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
(
|
(
|
||||||
6, // Anchor tags supported in SWF version 6 and later.
|
6, // Anchor tags supported in SWF version 6 and later.
|
||||||
Tag::FrameLabel(FrameLabel {
|
Tag::FrameLabel(FrameLabel {
|
||||||
label: "anchor_tag",
|
label: "anchor_tag".into(),
|
||||||
is_anchor: true,
|
is_anchor: true,
|
||||||
}),
|
}),
|
||||||
read_tag_bytes_from_file_with_index(
|
read_tag_bytes_from_file_with_index(
|
||||||
|
@ -2026,10 +2026,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
(
|
(
|
||||||
7,
|
7,
|
||||||
Tag::ImportAssets {
|
Tag::ImportAssets {
|
||||||
url: "ExportAssets-CS6.swf",
|
url: "ExportAssets-CS6.swf".into(),
|
||||||
imports: vec![ExportedAsset {
|
imports: vec![ExportedAsset {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "Test💯",
|
name: "Test💯".into(),
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
read_tag_bytes_from_file("tests/swfs/ImportAssets-CS6.swf", TagCode::ImportAssets),
|
read_tag_bytes_from_file("tests/swfs/ImportAssets-CS6.swf", TagCode::ImportAssets),
|
||||||
|
@ -2037,10 +2037,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
(
|
(
|
||||||
8,
|
8,
|
||||||
Tag::ImportAssets {
|
Tag::ImportAssets {
|
||||||
url: "ExportAssets-CS6.swf",
|
url: "ExportAssets-CS6.swf".into(),
|
||||||
imports: vec![ExportedAsset {
|
imports: vec![ExportedAsset {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "Test💯",
|
name: "Test💯".into(),
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
read_tag_bytes_from_file("tests/swfs/ImportAssets2-CS6.swf", TagCode::ImportAssets2),
|
read_tag_bytes_from_file("tests/swfs/ImportAssets2-CS6.swf", TagCode::ImportAssets2),
|
||||||
|
@ -2084,7 +2084,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
1,
|
1,
|
||||||
Tag::Metadata("aa!"),
|
Tag::Metadata(SwfStr::from_str_with_encoding("aa!", WINDOWS_1252).unwrap()),
|
||||||
vec![0b01_000100, 0b000_10011, b'a', b'a', b'!', 0],
|
vec![0b01_000100, 0b000_10011, b'a', b'a', b'!', 0],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -2237,7 +2237,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
b_add: 20,
|
b_add: 20,
|
||||||
}),
|
}),
|
||||||
ratio: None,
|
ratio: None,
|
||||||
name: Some("test"),
|
name: Some("test".into()),
|
||||||
clip_depth: None,
|
clip_depth: None,
|
||||||
class_name: None,
|
class_name: None,
|
||||||
filters: Some(vec![
|
filters: Some(vec![
|
||||||
|
@ -2388,7 +2388,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
5, // Password supported in SWF version 5 or later.
|
5, // Password supported in SWF version 5 or later.
|
||||||
Tag::Protect(Some("$1$d/$yMscKH17OJ0paJT.e67iz0")),
|
Tag::Protect(Some(
|
||||||
|
SwfStr::from_str_with_encoding("$1$d/$yMscKH17OJ0paJT.e67iz0", WINDOWS_1252)
|
||||||
|
.unwrap(),
|
||||||
|
)),
|
||||||
read_tag_bytes_from_file("tests/swfs/Protect.swf", TagCode::Protect),
|
read_tag_bytes_from_file("tests/swfs/Protect.swf", TagCode::Protect),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -2443,11 +2446,11 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
Tag::SymbolClass(vec![
|
Tag::SymbolClass(vec![
|
||||||
SymbolClassLink {
|
SymbolClassLink {
|
||||||
id: 2,
|
id: 2,
|
||||||
class_name: "foo.Test",
|
class_name: "foo.Test".into(),
|
||||||
},
|
},
|
||||||
SymbolClassLink {
|
SymbolClassLink {
|
||||||
id: 0,
|
id: 0,
|
||||||
class_name: "DocumentTest",
|
class_name: "DocumentTest".into(),
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
read_tag_bytes_from_file("tests/swfs/SymbolClass.swf", TagCode::SymbolClass),
|
read_tag_bytes_from_file("tests/swfs/SymbolClass.swf", TagCode::SymbolClass),
|
||||||
|
@ -2469,7 +2472,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
||||||
(
|
(
|
||||||
9,
|
9,
|
||||||
Tag::StartSound2 {
|
Tag::StartSound2 {
|
||||||
class_name: "TestSound",
|
class_name: "TestSound".into(),
|
||||||
sound_info: Box::new(SoundInfo {
|
sound_info: Box::new(SoundInfo {
|
||||||
event: SoundEvent::Event,
|
event: SoundEvent::Event,
|
||||||
in_sample: None,
|
in_sample: None,
|
||||||
|
@ -2670,8 +2673,8 @@ pub fn avm1_tests() -> Vec<Avm1TestData> {
|
||||||
(
|
(
|
||||||
3,
|
3,
|
||||||
Action::GetUrl {
|
Action::GetUrl {
|
||||||
url: "a",
|
url: SwfStr::from_str_with_encoding("a", WINDOWS_1252).unwrap(),
|
||||||
target: "b",
|
target: SwfStr::from_str_with_encoding("b", WINDOWS_1252).unwrap(),
|
||||||
},
|
},
|
||||||
vec![0x83, 4, 0, 97, 0, 98, 0],
|
vec![0x83, 4, 0, 97, 0, 98, 0],
|
||||||
),
|
),
|
||||||
|
@ -2713,7 +2716,7 @@ pub fn avm1_tests() -> Vec<Avm1TestData> {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
3,
|
3,
|
||||||
Action::GotoLabel("testb"),
|
Action::GotoLabel(SwfStr::from_str_with_encoding("testb", WINDOWS_1252).unwrap()),
|
||||||
vec![0x8C, 6, 0, 116, 101, 115, 116, 98, 0],
|
vec![0x8C, 6, 0, 116, 101, 115, 116, 98, 0],
|
||||||
),
|
),
|
||||||
(4, Action::If { offset: 1 }, vec![0x9D, 2, 0, 1, 0]),
|
(4, Action::If { offset: 1 }, vec![0x9D, 2, 0, 1, 0]),
|
||||||
|
@ -2733,7 +2736,9 @@ pub fn avm1_tests() -> Vec<Avm1TestData> {
|
||||||
(3, Action::PreviousFrame, vec![0x05]),
|
(3, Action::PreviousFrame, vec![0x05]),
|
||||||
(
|
(
|
||||||
4,
|
4,
|
||||||
Action::Push(vec![Value::Str("test")]),
|
Action::Push(vec![Value::Str(
|
||||||
|
SwfStr::from_str_with_encoding("test", WINDOWS_1252).unwrap(),
|
||||||
|
)]),
|
||||||
vec![0x96, 6, 0, 0, 116, 101, 115, 116, 0],
|
vec![0x96, 6, 0, 0, 116, 101, 115, 116, 0],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -2796,7 +2801,7 @@ pub fn avm1_tests() -> Vec<Avm1TestData> {
|
||||||
(4, Action::RandomNumber, vec![0x30]),
|
(4, Action::RandomNumber, vec![0x30]),
|
||||||
(
|
(
|
||||||
3,
|
3,
|
||||||
Action::SetTarget("test"),
|
Action::SetTarget(SwfStr::from_str_with_encoding("test", WINDOWS_1252).unwrap()),
|
||||||
vec![0x8B, 5, 0, 116, 101, 115, 116, 0],
|
vec![0x8B, 5, 0, 116, 101, 115, 116, 0],
|
||||||
),
|
),
|
||||||
(4, Action::SetVariable, vec![0x1D]),
|
(4, Action::SetVariable, vec![0x1D]),
|
||||||
|
@ -2845,8 +2850,11 @@ pub fn avm1_tests() -> Vec<Avm1TestData> {
|
||||||
(
|
(
|
||||||
5,
|
5,
|
||||||
Action::DefineFunction {
|
Action::DefineFunction {
|
||||||
name: "cliche",
|
name: SwfStr::from_str_with_encoding("cliche", WINDOWS_1252).unwrap(),
|
||||||
params: vec!["greeting", "name"],
|
params: vec![
|
||||||
|
SwfStr::from_str_with_encoding("greeting", WINDOWS_1252).unwrap(),
|
||||||
|
SwfStr::from_str_with_encoding("name", WINDOWS_1252).unwrap(),
|
||||||
|
],
|
||||||
actions: &[
|
actions: &[
|
||||||
0x96, 0x0a, 0x00, 0x00, 0x67, 0x72, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x00,
|
0x96, 0x0a, 0x00, 0x00, 0x67, 0x72, 0x65, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x00,
|
||||||
0x1c, 0x96, 0x03, 0x00, 0x00, 0x20, 0x00, 0x47, 0x96, 0x06, 0x00, 0x00, 0x6e,
|
0x1c, 0x96, 0x03, 0x00, 0x00, 0x20, 0x00, 0x47, 0x96, 0x06, 0x00, 0x00, 0x6e,
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//! These structures are documented in the Adobe SWF File Format Specification
|
//! These structures are documented in the Adobe SWF File Format Specification
|
||||||
//! version 19 (henceforth SWF19):
|
//! version 19 (henceforth SWF19):
|
||||||
//! https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf
|
//! https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf
|
||||||
|
use crate::string::SwfStr;
|
||||||
use enumset::{EnumSet, EnumSetType};
|
use enumset::{EnumSet, EnumSetType};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
@ -10,8 +11,6 @@ mod matrix;
|
||||||
|
|
||||||
pub use matrix::Matrix;
|
pub use matrix::Matrix;
|
||||||
|
|
||||||
pub type SwfStr<'a> = &'a str;
|
|
||||||
|
|
||||||
/// A complete header and tags in the SWF file.
|
/// A complete header and tags in the SWF file.
|
||||||
/// This is returned by the `swf::read_swf` convenience method.
|
/// This is returned by the `swf::read_swf` convenience method.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
|
|
@ -5,9 +5,12 @@
|
||||||
clippy::unreadable_literal
|
clippy::unreadable_literal
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::{
|
||||||
use crate::tag_code::TagCode;
|
error::{Error, Result},
|
||||||
use crate::types::*;
|
string::SwfStr,
|
||||||
|
tag_code::TagCode,
|
||||||
|
types::*,
|
||||||
|
};
|
||||||
use byteorder::{LittleEndian, WriteBytesExt};
|
use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
|
@ -185,7 +188,7 @@ pub trait SwfWrite<W: Write> {
|
||||||
self.get_inner().write_all(&num)
|
self.get_inner().write_all(&num)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_c_string(&mut self, s: &str) -> io::Result<()> {
|
fn write_string(&mut self, s: SwfStr<'_>) -> io::Result<()> {
|
||||||
self.get_inner().write_all(s.as_bytes())?;
|
self.get_inner().write_all(s.as_bytes())?;
|
||||||
self.write_u8(0)
|
self.write_u8(0)
|
||||||
}
|
}
|
||||||
|
@ -306,8 +309,8 @@ impl<W: Write> SwfWrite<W> for Writer<W> {
|
||||||
self.output.write_f64::<LittleEndian>(n)
|
self.output.write_f64::<LittleEndian>(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_c_string(&mut self, s: &str) -> io::Result<()> {
|
fn write_string(&mut self, s: SwfStr<'_>) -> io::Result<()> {
|
||||||
self.get_inner().write_all(s.as_bytes())?;
|
self.output.write_all(s.as_bytes())?;
|
||||||
self.write_u8(0)
|
self.write_u8(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -534,11 +537,11 @@ impl<W: Write> Writer<W> {
|
||||||
|
|
||||||
Tag::ExportAssets(ref exports) => self.write_export_assets(&exports[..])?,
|
Tag::ExportAssets(ref exports) => self.write_export_assets(&exports[..])?,
|
||||||
|
|
||||||
Tag::Protect(ref password) => {
|
Tag::Protect(password) => {
|
||||||
if let Some(ref password_md5) = *password {
|
if let Some(password_md5) = password {
|
||||||
self.write_tag_header(TagCode::Protect, password_md5.len() as u32 + 3)?;
|
self.write_tag_header(TagCode::Protect, password_md5.len() as u32 + 3)?;
|
||||||
self.write_u16(0)?; // Two null bytes? Not specified in SWF19.
|
self.write_u16(0)?; // Two null bytes? Not specified in SWF19.
|
||||||
self.write_c_string(password_md5)?;
|
self.write_string(password_md5)?;
|
||||||
} else {
|
} else {
|
||||||
self.write_tag_header(TagCode::Protect, 0)?;
|
self.write_tag_header(TagCode::Protect, 0)?;
|
||||||
}
|
}
|
||||||
|
@ -782,14 +785,14 @@ impl<W: Write> Writer<W> {
|
||||||
|
|
||||||
Tag::DefineFontName {
|
Tag::DefineFontName {
|
||||||
id,
|
id,
|
||||||
ref name,
|
name,
|
||||||
ref copyright_info,
|
copyright_info,
|
||||||
} => {
|
} => {
|
||||||
let len = name.len() + copyright_info.len() + 4;
|
let len = name.len() + copyright_info.len() + 4;
|
||||||
self.write_tag_header(TagCode::DefineFontName, len as u32)?;
|
self.write_tag_header(TagCode::DefineFontName, len as u32)?;
|
||||||
self.write_character_id(id)?;
|
self.write_character_id(id)?;
|
||||||
self.write_c_string(name)?;
|
self.write_string(name)?;
|
||||||
self.write_c_string(copyright_info)?;
|
self.write_string(copyright_info)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tag::DefineMorphShape(ref define_morph_shape) => {
|
Tag::DefineMorphShape(ref define_morph_shape) => {
|
||||||
|
@ -819,7 +822,7 @@ impl<W: Write> Writer<W> {
|
||||||
let len = do_abc.data.len() + do_abc.name.len() + 5;
|
let len = do_abc.data.len() + do_abc.name.len() + 5;
|
||||||
self.write_tag_header(TagCode::DoAbc, len as u32)?;
|
self.write_tag_header(TagCode::DoAbc, len as u32)?;
|
||||||
self.write_u32(if do_abc.is_lazy_initialize { 1 } else { 0 })?;
|
self.write_u32(if do_abc.is_lazy_initialize { 1 } else { 0 })?;
|
||||||
self.write_c_string(&do_abc.name)?;
|
self.write_string(do_abc.name)?;
|
||||||
self.output.write_all(&do_abc.data)?;
|
self.output.write_all(&do_abc.data)?;
|
||||||
}
|
}
|
||||||
Tag::DoAction(ref action_data) => {
|
Tag::DoAction(ref action_data) => {
|
||||||
|
@ -835,7 +838,7 @@ impl<W: Write> Writer<W> {
|
||||||
self.output.write_all(action_data)?;
|
self.output.write_all(action_data)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tag::EnableDebugger(ref password_md5) => {
|
Tag::EnableDebugger(password_md5) => {
|
||||||
let len = password_md5.len() as u32 + 1;
|
let len = password_md5.len() as u32 + 1;
|
||||||
if self.version >= 6 {
|
if self.version >= 6 {
|
||||||
// SWF v6+ uses EnableDebugger2 tag.
|
// SWF v6+ uses EnableDebugger2 tag.
|
||||||
|
@ -845,7 +848,7 @@ impl<W: Write> Writer<W> {
|
||||||
self.write_tag_header(TagCode::EnableDebugger, len)?;
|
self.write_tag_header(TagCode::EnableDebugger, len)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.write_c_string(password_md5)?;
|
self.write_string(password_md5)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tag::EnableTelemetry { ref password_hash } => {
|
Tag::EnableTelemetry { ref password_hash } => {
|
||||||
|
@ -861,10 +864,7 @@ impl<W: Write> Writer<W> {
|
||||||
|
|
||||||
Tag::End => self.write_tag_header(TagCode::End, 0)?,
|
Tag::End => self.write_tag_header(TagCode::End, 0)?,
|
||||||
|
|
||||||
Tag::ImportAssets {
|
Tag::ImportAssets { url, ref imports } => {
|
||||||
ref url,
|
|
||||||
ref imports,
|
|
||||||
} => {
|
|
||||||
let len = imports.iter().map(|e| e.name.len() as u32 + 3).sum::<u32>()
|
let len = imports.iter().map(|e| e.name.len() as u32 + 3).sum::<u32>()
|
||||||
+ url.len() as u32
|
+ url.len() as u32
|
||||||
+ 1
|
+ 1
|
||||||
|
@ -872,17 +872,17 @@ impl<W: Write> Writer<W> {
|
||||||
// SWF v8 and later use ImportAssets2 tag.
|
// SWF v8 and later use ImportAssets2 tag.
|
||||||
if self.version >= 8 {
|
if self.version >= 8 {
|
||||||
self.write_tag_header(TagCode::ImportAssets2, len + 2)?;
|
self.write_tag_header(TagCode::ImportAssets2, len + 2)?;
|
||||||
self.write_c_string(url)?;
|
self.write_string(url)?;
|
||||||
self.write_u8(1)?;
|
self.write_u8(1)?;
|
||||||
self.write_u8(0)?;
|
self.write_u8(0)?;
|
||||||
} else {
|
} else {
|
||||||
self.write_tag_header(TagCode::ImportAssets, len)?;
|
self.write_tag_header(TagCode::ImportAssets, len)?;
|
||||||
self.write_c_string(url)?;
|
self.write_string(url)?;
|
||||||
}
|
}
|
||||||
self.write_u16(imports.len() as u16)?;
|
self.write_u16(imports.len() as u16)?;
|
||||||
for &ExportedAsset { id, ref name } in imports {
|
for &ExportedAsset { id, name } in imports {
|
||||||
self.write_u16(id)?;
|
self.write_u16(id)?;
|
||||||
self.write_c_string(name)?;
|
self.write_string(name)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -891,9 +891,9 @@ impl<W: Write> Writer<W> {
|
||||||
self.output.write_all(data)?;
|
self.output.write_all(data)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tag::Metadata(ref metadata) => {
|
Tag::Metadata(metadata) => {
|
||||||
self.write_tag_header(TagCode::Metadata, metadata.len() as u32 + 1)?;
|
self.write_tag_header(TagCode::Metadata, metadata.len() as u32 + 1)?;
|
||||||
self.write_c_string(metadata)?;
|
self.write_string(metadata)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Allow clone of color.
|
// TODO: Allow clone of color.
|
||||||
|
@ -969,7 +969,7 @@ impl<W: Write> Writer<W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Tag::StartSound2 {
|
Tag::StartSound2 {
|
||||||
ref class_name,
|
class_name,
|
||||||
ref sound_info,
|
ref sound_info,
|
||||||
} => {
|
} => {
|
||||||
let length = class_name.len() as u32
|
let length = class_name.len() as u32
|
||||||
|
@ -987,7 +987,7 @@ impl<W: Write> Writer<W> {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
self.write_tag_header(TagCode::StartSound2, length)?;
|
self.write_tag_header(TagCode::StartSound2, length)?;
|
||||||
self.write_c_string(class_name)?;
|
self.write_string(class_name)?;
|
||||||
self.write_sound_info(sound_info)?;
|
self.write_sound_info(sound_info)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -999,9 +999,9 @@ impl<W: Write> Writer<W> {
|
||||||
+ 2;
|
+ 2;
|
||||||
self.write_tag_header(TagCode::SymbolClass, len)?;
|
self.write_tag_header(TagCode::SymbolClass, len)?;
|
||||||
self.write_u16(symbols.len() as u16)?;
|
self.write_u16(symbols.len() as u16)?;
|
||||||
for &SymbolClassLink { id, ref class_name } in symbols {
|
for &SymbolClassLink { id, class_name } in symbols {
|
||||||
self.write_u16(id)?;
|
self.write_u16(id)?;
|
||||||
self.write_c_string(class_name)?;
|
self.write_string(class_name)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1033,15 +1033,12 @@ impl<W: Write> Writer<W> {
|
||||||
self.write_u32(flags)?;
|
self.write_u32(flags)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tag::FrameLabel(FrameLabel {
|
Tag::FrameLabel(FrameLabel { label, is_anchor }) => {
|
||||||
ref label,
|
|
||||||
is_anchor,
|
|
||||||
}) => {
|
|
||||||
// TODO: Assert proper version
|
// TODO: Assert proper version
|
||||||
let is_anchor = is_anchor && self.version >= 6;
|
let is_anchor = is_anchor && self.version >= 6;
|
||||||
let length = label.len() as u32 + if is_anchor { 2 } else { 1 };
|
let length = label.len() as u32 + if is_anchor { 2 } else { 1 };
|
||||||
self.write_tag_header(TagCode::FrameLabel, length)?;
|
self.write_tag_header(TagCode::FrameLabel, length)?;
|
||||||
self.write_c_string(label)?;
|
self.write_string(label)?;
|
||||||
if is_anchor {
|
if is_anchor {
|
||||||
self.write_u8(1)?;
|
self.write_u8(1)?;
|
||||||
}
|
}
|
||||||
|
@ -1506,12 +1503,12 @@ impl<W: Write> Writer<W> {
|
||||||
writer.write_encoded_u32(data.scenes.len() as u32)?;
|
writer.write_encoded_u32(data.scenes.len() as u32)?;
|
||||||
for scene in &data.scenes {
|
for scene in &data.scenes {
|
||||||
writer.write_encoded_u32(scene.frame_num)?;
|
writer.write_encoded_u32(scene.frame_num)?;
|
||||||
writer.write_c_string(&scene.label)?;
|
writer.write_string(scene.label)?;
|
||||||
}
|
}
|
||||||
writer.write_encoded_u32(data.frame_labels.len() as u32)?;
|
writer.write_encoded_u32(data.frame_labels.len() as u32)?;
|
||||||
for frame_label in &data.frame_labels {
|
for frame_label in &data.frame_labels {
|
||||||
writer.write_encoded_u32(frame_label.frame_num)?;
|
writer.write_encoded_u32(frame_label.frame_num)?;
|
||||||
writer.write_c_string(&frame_label.label)?;
|
writer.write_string(frame_label.label)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.write_tag_header(TagCode::DefineSceneAndFrameLabelData, buf.len() as u32)?;
|
self.write_tag_header(TagCode::DefineSceneAndFrameLabelData, buf.len() as u32)?;
|
||||||
|
@ -1596,9 +1593,9 @@ impl<W: Write> Writer<W> {
|
||||||
+ 2;
|
+ 2;
|
||||||
self.write_tag_header(TagCode::ExportAssets, len)?;
|
self.write_tag_header(TagCode::ExportAssets, len)?;
|
||||||
self.write_u16(exports.len() as u16)?;
|
self.write_u16(exports.len() as u16)?;
|
||||||
for &ExportedAsset { id, ref name } in exports {
|
for &ExportedAsset { id, name } in exports {
|
||||||
self.write_u16(id)?;
|
self.write_u16(id)?;
|
||||||
self.write_c_string(name)?;
|
self.write_string(name)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2035,8 +2032,8 @@ impl<W: Write> Writer<W> {
|
||||||
writer.write_u16(place_object.depth)?;
|
writer.write_u16(place_object.depth)?;
|
||||||
|
|
||||||
if place_object_version >= 3 {
|
if place_object_version >= 3 {
|
||||||
if let Some(ref class_name) = place_object.class_name {
|
if let Some(class_name) = place_object.class_name {
|
||||||
writer.write_c_string(class_name)?;
|
writer.write_string(class_name)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2054,8 +2051,8 @@ impl<W: Write> Writer<W> {
|
||||||
if let Some(ratio) = place_object.ratio {
|
if let Some(ratio) = place_object.ratio {
|
||||||
writer.write_u16(ratio)?;
|
writer.write_u16(ratio)?;
|
||||||
}
|
}
|
||||||
if let Some(ref name) = place_object.name {
|
if let Some(name) = place_object.name {
|
||||||
writer.write_c_string(name)?;
|
writer.write_string(name)?;
|
||||||
};
|
};
|
||||||
if let Some(clip_depth) = place_object.clip_depth {
|
if let Some(clip_depth) = place_object.clip_depth {
|
||||||
writer.write_u16(clip_depth)?;
|
writer.write_u16(clip_depth)?;
|
||||||
|
@ -2521,7 +2518,7 @@ impl<W: Write> Writer<W> {
|
||||||
| if font.is_italic { 0b10 } else { 0 }
|
| if font.is_italic { 0b10 } else { 0 }
|
||||||
| if font.is_bold { 0b1 } else { 0 },
|
| if font.is_bold { 0b1 } else { 0 },
|
||||||
)?;
|
)?;
|
||||||
self.write_c_string(&font.name)?;
|
self.write_string(font.name)?;
|
||||||
if let Some(ref data) = font.data {
|
if let Some(ref data) = font.data {
|
||||||
self.output.write_all(data)?;
|
self.output.write_all(data)?;
|
||||||
}
|
}
|
||||||
|
@ -2647,8 +2644,8 @@ impl<W: Write> Writer<W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(Herschel): Check SWF version.
|
// TODO(Herschel): Check SWF version.
|
||||||
if let Some(ref class) = edit_text.font_class_name {
|
if let Some(class) = edit_text.font_class_name {
|
||||||
writer.write_c_string(class)?;
|
writer.write_string(class)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(Herschel): Height only exists iff HasFontId, maybe for HasFontClass too?
|
// TODO(Herschel): Height only exists iff HasFontId, maybe for HasFontClass too?
|
||||||
|
@ -2677,9 +2674,9 @@ impl<W: Write> Writer<W> {
|
||||||
writer.write_i16(layout.leading.get() as i16)?;
|
writer.write_i16(layout.leading.get() as i16)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.write_c_string(&edit_text.variable_name)?;
|
writer.write_string(edit_text.variable_name)?;
|
||||||
if let Some(ref text) = edit_text.initial_text {
|
if let Some(text) = edit_text.initial_text {
|
||||||
writer.write_c_string(text)?;
|
writer.write_string(text)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2993,7 +2990,7 @@ mod tests {
|
||||||
{
|
{
|
||||||
// TODO: What if I use a cursor instead of buf ?
|
// TODO: What if I use a cursor instead of buf ?
|
||||||
let mut writer = Writer::new(&mut buf, 1);
|
let mut writer = Writer::new(&mut buf, 1);
|
||||||
writer.write_c_string("Hello!").unwrap();
|
writer.write_string("Hello!".into()).unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(buf, "Hello!\0".bytes().collect::<Vec<_>>());
|
assert_eq!(buf, "Hello!\0".bytes().collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
|
@ -3003,7 +3000,7 @@ mod tests {
|
||||||
{
|
{
|
||||||
// TODO: What if I use a cursor instead of buf ?
|
// TODO: What if I use a cursor instead of buf ?
|
||||||
let mut writer = Writer::new(&mut buf, 1);
|
let mut writer = Writer::new(&mut buf, 1);
|
||||||
writer.write_c_string("😀😂!🐼").unwrap();
|
writer.write_string("😀😂!🐼".into()).unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(buf, "😀😂!🐼\0".bytes().collect::<Vec<_>>());
|
assert_eq!(buf, "😀😂!🐼\0".bytes().collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue