swf: Add SwfStr type to handle encodings in SWF<6

This commit is contained in:
Mike Welsh 2021-01-18 13:17:17 -08:00
parent 19034b76e4
commit 1d9c11e145
17 changed files with 459 additions and 263 deletions

1
Cargo.lock generated
View File

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

View File

@ -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,
&params[..], &params[..],
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(())
}
} }

View File

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

View File

@ -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());

View File

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

View File

@ -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);

View File

@ -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);

View File

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

View File

@ -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()
)])
);
} }
} }

View File

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

View File

@ -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(&param.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)?;

View File

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

View File

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

141
swf/src/string.rs Normal file
View File

@ -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())
}
}

View File

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

View File

@ -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)]

View File

@ -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<_>>());
} }