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 = [
|
||||
"approx",
|
||||
"byteorder",
|
||||
"encoding_rs",
|
||||
"enumset",
|
||||
"flate2",
|
||||
"libflate 1.0.3",
|
||||
|
|
|
@ -25,6 +25,7 @@ use std::convert::TryFrom;
|
|||
use std::fmt;
|
||||
use swf::avm1::read::Reader;
|
||||
use swf::avm1::types::{Action, CatchVar, Function, TryBlock};
|
||||
use swf::SwfStr;
|
||||
use url::form_urlencoded;
|
||||
|
||||
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>> {
|
||||
let mut read = Reader::new(&code.movie.data()[..], self.swf_version());
|
||||
read.seek(code.start as isize);
|
||||
let mut read = Reader::new(&code.movie.data()[code.start..], self.swf_version());
|
||||
|
||||
loop {
|
||||
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.
|
||||
fn do_action(
|
||||
fn do_action<'b>(
|
||||
&mut self,
|
||||
data: &SwfSlice,
|
||||
reader: &mut Reader<'_>,
|
||||
data: &'b SwfSlice,
|
||||
reader: &mut Reader<'b>,
|
||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
self.actions_since_timeout_check += 1;
|
||||
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.
|
||||
Ok(FrameControl::Return(ReturnType::Implicit))
|
||||
} else if let Some(action) = reader.read_action()? {
|
||||
|
@ -496,7 +496,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
params,
|
||||
actions,
|
||||
} => self.action_define_function(
|
||||
&name,
|
||||
name,
|
||||
¶ms[..],
|
||||
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::GetTime => self.action_get_time(),
|
||||
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 {
|
||||
send_vars_method,
|
||||
is_target_sprite,
|
||||
|
@ -528,14 +528,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
scene_offset,
|
||||
} => self.action_goto_frame_2(set_playing, scene_offset),
|
||||
Action::Greater => self.action_greater(),
|
||||
Action::GotoLabel(label) => self.action_goto_label(&label),
|
||||
Action::If { offset } => self.action_if(offset, reader),
|
||||
Action::GotoLabel(label) => self.action_goto_label(label),
|
||||
Action::If { offset } => self.action_if(offset, reader, data),
|
||||
Action::Increment => self.action_increment(),
|
||||
Action::InitArray => self.action_init_array(),
|
||||
Action::InitObject => self.action_init_object(),
|
||||
Action::ImplementsOp => self.action_implements_op(),
|
||||
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::Less2 => self.action_less_2(),
|
||||
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::SetMember => self.action_set_member(),
|
||||
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::SetVariable => self.action_set_variable(),
|
||||
Action::StackSwap => self.action_stack_swap(),
|
||||
|
@ -889,11 +889,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
|
||||
fn action_constant_pool(
|
||||
&mut self,
|
||||
constant_pool: &[&str],
|
||||
constant_pool: &[SwfStr<'_>],
|
||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
self.context.avm1.constant_pool = GcCell::allocate(
|
||||
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);
|
||||
|
||||
|
@ -908,10 +911,12 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
|
||||
fn action_define_function(
|
||||
&mut self,
|
||||
name: &str,
|
||||
params: &[&str],
|
||||
name: SwfStr<'_>,
|
||||
params: &[SwfStr<'_>],
|
||||
actions: SwfSlice,
|
||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
let name = name.to_str_lossy();
|
||||
let name = name.as_ref();
|
||||
let swf_version = self.swf_version();
|
||||
let scope = Scope::new_closure_scope(self.scope_cell(), self.context.gc_context);
|
||||
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() {
|
||||
self.context.avm1.push(func_obj);
|
||||
} else {
|
||||
self.define(action_func.name, func_obj);
|
||||
self.define(&action_func.name.to_str_lossy(), func_obj);
|
||||
}
|
||||
|
||||
Ok(FrameControl::Continue)
|
||||
|
@ -1213,9 +1218,15 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
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 {
|
||||
let url = url.to_string();
|
||||
match target[6..].parse::<u32>() {
|
||||
Ok(level_id) => {
|
||||
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);
|
||||
}
|
||||
|
||||
if let Some(fscommand) = fscommand::parse(url) {
|
||||
if let Some(fscommand) = fscommand::parse(&url) {
|
||||
fscommand::handle(fscommand, self)?;
|
||||
} else {
|
||||
self.context
|
||||
|
@ -1414,10 +1425,10 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
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) = 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);
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
|
||||
fn action_if(
|
||||
fn action_if<'b>(
|
||||
&mut self,
|
||||
jump_offset: i16,
|
||||
reader: &mut Reader<'_>,
|
||||
reader: &mut Reader<'b>,
|
||||
data: &'b SwfSlice,
|
||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
let val = self.context.avm1.pop();
|
||||
if val.as_bool(self.current_swf_version()) {
|
||||
reader.seek(jump_offset.into());
|
||||
self.seek(jump_offset, reader, data)?;
|
||||
}
|
||||
Ok(FrameControl::Continue)
|
||||
}
|
||||
|
@ -1515,13 +1527,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
Ok(FrameControl::Continue)
|
||||
}
|
||||
|
||||
fn action_jump(
|
||||
fn action_jump<'b>(
|
||||
&mut self,
|
||||
jump_offset: i16,
|
||||
reader: &mut Reader<'_>,
|
||||
reader: &mut Reader<'b>,
|
||||
data: &'b SwfSlice,
|
||||
) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
// TODO(Herschel): Handle out-of-bounds.
|
||||
reader.seek(jump_offset.into());
|
||||
self.seek(jump_offset, reader, data)?;
|
||||
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::Double(v) => (*v).into(),
|
||||
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::ConstantPool(i) => {
|
||||
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 {
|
||||
avm_warn!(
|
||||
self,
|
||||
|
@ -2267,7 +2279,9 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
);
|
||||
|
||||
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) => {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use swf::avm1::types::FunctionParam;
|
||||
use swf::{avm1::types::FunctionParam, SwfStr};
|
||||
|
||||
/// Represents a function defined in Ruffle's code.
|
||||
///
|
||||
|
@ -94,14 +94,15 @@ impl<'gc> Avm1Function<'gc> {
|
|||
swf_version: u8,
|
||||
actions: SwfSlice,
|
||||
name: &str,
|
||||
params: &[&str],
|
||||
params: &[SwfStr<'_>],
|
||||
scope: GcCell<'gc, Scope<'gc>>,
|
||||
constant_pool: GcCell<'gc, Vec<String>>,
|
||||
base_clip: DisplayObject<'gc>,
|
||||
) -> Self {
|
||||
let name = match name {
|
||||
"" => None,
|
||||
name => Some(name.to_string()),
|
||||
let name = if name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(name.to_string())
|
||||
};
|
||||
|
||||
Avm1Function {
|
||||
|
@ -118,7 +119,10 @@ impl<'gc> Avm1Function<'gc> {
|
|||
suppress_this: false,
|
||||
preload_this: 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,
|
||||
constant_pool,
|
||||
base_clip,
|
||||
|
@ -134,9 +138,10 @@ impl<'gc> Avm1Function<'gc> {
|
|||
constant_pool: GcCell<'gc, Vec<String>>,
|
||||
base_clip: DisplayObject<'gc>,
|
||||
) -> Self {
|
||||
let name = match swf_function.name {
|
||||
"" => None,
|
||||
name => Some(name.to_string()),
|
||||
let name = if swf_function.name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(swf_function.name.to_string_lossy())
|
||||
};
|
||||
|
||||
let mut owned_params = Vec::new();
|
||||
|
@ -145,7 +150,7 @@ impl<'gc> Avm1Function<'gc> {
|
|||
register_index: r,
|
||||
} in &swf_function.params
|
||||
{
|
||||
owned_params.push((*r, (*s).to_string()))
|
||||
owned_params.push((*r, (*s).to_string_lossy()))
|
||||
}
|
||||
|
||||
Avm1Function {
|
||||
|
|
|
@ -841,7 +841,7 @@ pub trait TDisplayObject<'gc>:
|
|||
self.set_color_transform(gc_context, &color_transform.clone().into());
|
||||
}
|
||||
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 {
|
||||
self.set_clip_depth(gc_context, clip_depth.into());
|
||||
|
|
|
@ -156,6 +156,7 @@ impl<'gc> EditText<'gc> {
|
|||
let mut text_spans = FormatSpans::new();
|
||||
text_spans.set_default_format(default_format.clone());
|
||||
|
||||
let text = text.to_str_lossy();
|
||||
if is_html {
|
||||
let _ = document
|
||||
.as_node()
|
||||
|
@ -211,13 +212,13 @@ impl<'gc> EditText<'gc> {
|
|||
id: swf_tag.id,
|
||||
bounds: swf_tag.bounds,
|
||||
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,
|
||||
color: swf_tag.color.clone(),
|
||||
max_length: swf_tag.max_length,
|
||||
layout: swf_tag.layout.clone(),
|
||||
variable_name: swf_tag.variable_name.to_string(),
|
||||
initial_text: swf_tag.initial_text.map(str::to_string),
|
||||
variable_name: swf_tag.variable_name.to_string_lossy(),
|
||||
initial_text: swf_tag.initial_text.map(|s| s.to_string_lossy()),
|
||||
is_word_wrap: swf_tag.is_word_wrap,
|
||||
is_multiline: swf_tag.is_multiline,
|
||||
is_password: swf_tag.is_password,
|
||||
|
@ -246,7 +247,7 @@ impl<'gc> EditText<'gc> {
|
|||
intrinsic_bounds,
|
||||
bounds,
|
||||
autosize: AutoSizeMode::None,
|
||||
variable: variable.map(str::to_string),
|
||||
variable: variable.map(|s| s.to_string_lossy()),
|
||||
bound_stage_object: None,
|
||||
firing_variable_binding: false,
|
||||
selection: None,
|
||||
|
@ -293,7 +294,7 @@ impl<'gc> EditText<'gc> {
|
|||
indent: 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,
|
||||
is_word_wrap: 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
|
||||
// *entire* parsing code here. This sucks.
|
||||
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 domain = library.avm2_domain();
|
||||
|
||||
|
@ -499,7 +499,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
|
||||
for _ in 0..num_symbols {
|
||||
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) =
|
||||
Avm2QName::from_symbol_class(&class_name, activation.context.gc_context)
|
||||
|
@ -2458,7 +2458,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
|
|||
let font = swf::Font {
|
||||
id: font.id,
|
||||
version: 0,
|
||||
name: "",
|
||||
name: "".into(),
|
||||
glyphs,
|
||||
language: swf::Language::Unknown,
|
||||
layout: None,
|
||||
|
@ -2609,7 +2609,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
|
|||
let character = context
|
||||
.library
|
||||
.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?
|
||||
if let Some(Character::MovieClip(movie_clip)) = character {
|
||||
|
@ -2631,7 +2631,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
|
|||
) -> DecodeResult {
|
||||
let frame_label = reader.read_frame_label(tag_len)?;
|
||||
// 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)
|
||||
{
|
||||
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_class = et
|
||||
.font_class_name
|
||||
.map(str::to_string)
|
||||
.map(|s| s.to_string_lossy())
|
||||
.or_else(|| font.map(|font| font.descriptor().class().to_string()))
|
||||
.unwrap_or_else(|| "Times New Roman".to_string());
|
||||
let align = et.layout.clone().map(|l| l.align);
|
||||
|
|
|
@ -11,6 +11,7 @@ description = "Read and write the Adobe Flash SWF file format."
|
|||
|
||||
[dependencies]
|
||||
byteorder = "1.4"
|
||||
encoding_rs = "0.8.26"
|
||||
enumset = "1.0.1"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
|
|
|
@ -1,89 +1,97 @@
|
|||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
use crate::avm1::opcode::OpCode;
|
||||
use crate::avm1::types::*;
|
||||
use crate::avm1::{opcode::OpCode, types::*};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::read::SwfRead;
|
||||
use std::io::Cursor;
|
||||
use crate::string::SwfStr;
|
||||
use std::io;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Reader<'a> {
|
||||
inner: Cursor<&'a [u8]>,
|
||||
input: &'a [u8],
|
||||
version: u8,
|
||||
encoding: &'static encoding_rs::Encoding,
|
||||
}
|
||||
|
||||
impl<'a> SwfRead<Cursor<&'a [u8]>> for Reader<'a> {
|
||||
fn get_inner(&mut self) -> &mut Cursor<&'a [u8]> {
|
||||
&mut self.inner
|
||||
impl<'a> SwfRead<&'a [u8]> for Reader<'a> {
|
||||
fn get_inner(&mut self) -> &mut &'a [u8] {
|
||||
&mut self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
pub fn new(input: &'a [u8], version: u8) -> Self {
|
||||
Self {
|
||||
inner: Cursor::new(input),
|
||||
input,
|
||||
version,
|
||||
encoding: if version > 5 {
|
||||
encoding_rs::UTF_8
|
||||
} else {
|
||||
// TODO: Allow configurable encoding
|
||||
encoding_rs::WINDOWS_1252
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pos(&self) -> usize {
|
||||
self.inner.position() as usize
|
||||
pub fn get_ref(&self) -> &'a [u8] {
|
||||
self.input
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn seek(&mut self, relative_offset: isize) {
|
||||
let new_pos = self.inner.position() as i64 + relative_offset as i64;
|
||||
self.inner.set_position(new_pos as u64);
|
||||
pub fn get_mut(&mut self) -> &mut &'a [u8] {
|
||||
&mut self.input
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read_slice(&mut self, len: usize) -> Result<&'a [u8]> {
|
||||
let pos = self.pos();
|
||||
self.inner.set_position(pos as u64 + len as u64);
|
||||
let slice = self.inner.get_ref().get(pos..pos + len).ok_or_else(|| {
|
||||
std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "Buffer underrun")
|
||||
})?;
|
||||
Ok(slice)
|
||||
fn read_slice(&mut self, len: usize) -> io::Result<&'a [u8]> {
|
||||
if self.input.len() >= len {
|
||||
let slice = &self.input[..len];
|
||||
self.input = &self.input[len..];
|
||||
Ok(slice)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"Not enough data for slice",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn read_c_string(&mut self) -> Result<&'a str> {
|
||||
// Find zero terminator.
|
||||
let str_slice = {
|
||||
let start_pos = self.pos();
|
||||
loop {
|
||||
let byte = self.read_u8()?;
|
||||
if byte == 0 {
|
||||
break;
|
||||
}
|
||||
pub fn read_string(&mut self) -> io::Result<SwfStr<'a>> {
|
||||
let mut pos = 0;
|
||||
loop {
|
||||
let byte = *self.input.get(pos).ok_or(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"Not enough data for slice",
|
||||
))?;
|
||||
if byte == 0 {
|
||||
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?
|
||||
// Do we silently let it pass?
|
||||
// TODO: Verify ANSI for SWF 5 and earlier.
|
||||
std::str::from_utf8(str_slice).map_err(|_| Error::invalid_data("Invalid string data"))
|
||||
self.input = &self.input[pos + 1..];
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_action(&mut self) -> Result<Option<Action<'a>>> {
|
||||
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 end_pos = (start.as_ptr() as usize + length) as *const u8;
|
||||
if let Err(e) = action {
|
||||
return Err(Error::avm1_parse_error_with_source(opcode, e));
|
||||
}
|
||||
|
||||
// Verify that we parsed the correct amount of data.
|
||||
let end_pos = start_pos + length;
|
||||
let pos = self.pos();
|
||||
if pos != end_pos {
|
||||
if self.input.as_ptr() != end_pos {
|
||||
self.input = &start[length.min(start.len())..];
|
||||
// We incorrectly parsed this action.
|
||||
// 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));
|
||||
}
|
||||
action
|
||||
|
@ -131,7 +139,7 @@ impl<'a> Reader<'a> {
|
|||
OpCode::ConstantPool => {
|
||||
let mut constants = vec![];
|
||||
for _ in 0..self.read_u16()? {
|
||||
constants.push(self.read_c_string()?);
|
||||
constants.push(self.read_string()?);
|
||||
}
|
||||
Action::ConstantPool(constants)
|
||||
}
|
||||
|
@ -153,8 +161,8 @@ impl<'a> Reader<'a> {
|
|||
OpCode::GetProperty => Action::GetProperty,
|
||||
OpCode::GetTime => Action::GetTime,
|
||||
OpCode::GetUrl => Action::GetUrl {
|
||||
url: self.read_c_string()?,
|
||||
target: self.read_c_string()?,
|
||||
url: self.read_string()?,
|
||||
target: self.read_string()?,
|
||||
},
|
||||
OpCode::GetUrl2 => {
|
||||
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::If => Action::If {
|
||||
offset: self.read_i16()?,
|
||||
|
@ -226,7 +234,7 @@ impl<'a> Reader<'a> {
|
|||
OpCode::Return => Action::Return,
|
||||
OpCode::SetMember => Action::SetMember,
|
||||
OpCode::SetProperty => Action::SetProperty,
|
||||
OpCode::SetTarget => Action::SetTarget(self.read_c_string()?),
|
||||
OpCode::SetTarget => Action::SetTarget(self.read_string()?),
|
||||
OpCode::SetTarget2 => Action::SetTarget2,
|
||||
OpCode::SetVariable => Action::SetVariable,
|
||||
OpCode::StackSwap => Action::StackSwap,
|
||||
|
@ -281,9 +289,9 @@ impl<'a> Reader<'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);
|
||||
while self.pos() < end_pos {
|
||||
while self.input.as_ptr() < end_pos {
|
||||
values.push(self.read_push_value()?);
|
||||
}
|
||||
Ok(Action::Push(values))
|
||||
|
@ -291,7 +299,7 @@ impl<'a> Reader<'a> {
|
|||
|
||||
fn read_push_value(&mut self) -> Result<Value<'a>> {
|
||||
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()?),
|
||||
2 => Value::Null,
|
||||
3 => Value::Undefined,
|
||||
|
@ -307,11 +315,11 @@ impl<'a> Reader<'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 mut params = Vec::with_capacity(num_params as usize);
|
||||
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.
|
||||
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>> {
|
||||
let name = self.read_c_string()?;
|
||||
let name = self.read_string()?;
|
||||
let num_params = self.read_u16()?;
|
||||
let register_count = self.read_u8()?; // Number of registers
|
||||
let flags = self.read_u16()?;
|
||||
|
@ -332,7 +340,7 @@ impl<'a> Reader<'a> {
|
|||
for _ in 0..num_params {
|
||||
let register = self.read_u8()?;
|
||||
params.push(FunctionParam {
|
||||
name: self.read_c_string()?,
|
||||
name: self.read_string()?,
|
||||
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()?);
|
||||
*length += try_length + catch_length + finally_length;
|
||||
let catch_var = if flags & 0b100 == 0 {
|
||||
CatchVar::Var(self.read_c_string()?)
|
||||
CatchVar::Var(self.read_string()?)
|
||||
} else {
|
||||
CatchVar::Register(self.read_u8()?)
|
||||
};
|
||||
|
@ -421,6 +429,7 @@ pub mod tests {
|
|||
|
||||
#[test]
|
||||
fn read_define_function() {
|
||||
use encoding_rs::WINDOWS_1252;
|
||||
// Ensure we read a function properly along with the function data.
|
||||
let action_bytes = vec![
|
||||
0x9b, 0x08, 0x00, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x96, 0x06, 0x00,
|
||||
|
@ -431,7 +440,7 @@ pub mod tests {
|
|||
assert_eq!(
|
||||
action,
|
||||
Action::DefineFunction {
|
||||
name: "foo",
|
||||
name: SwfStr::from_str_with_encoding("foo", WINDOWS_1252).unwrap(),
|
||||
params: vec![],
|
||||
actions: &[0x96, 0x06, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00, 0x26],
|
||||
}
|
||||
|
@ -440,7 +449,12 @@ pub mod tests {
|
|||
if let Action::DefineFunction { actions, .. } = action {
|
||||
let mut reader = Reader::new(actions, 5);
|
||||
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)]
|
||||
pub enum Action<'a> {
|
||||
Add,
|
||||
|
@ -16,11 +18,11 @@ pub enum Action<'a> {
|
|||
CastOp,
|
||||
CharToAscii,
|
||||
CloneSprite,
|
||||
ConstantPool(Vec<&'a str>),
|
||||
ConstantPool(Vec<SwfStr<'a>>),
|
||||
Decrement,
|
||||
DefineFunction {
|
||||
name: &'a str,
|
||||
params: Vec<&'a str>,
|
||||
name: SwfStr<'a>,
|
||||
params: Vec<SwfStr<'a>>,
|
||||
actions: &'a [u8],
|
||||
},
|
||||
DefineFunction2(Function<'a>),
|
||||
|
@ -39,8 +41,8 @@ pub enum Action<'a> {
|
|||
GetProperty,
|
||||
GetTime,
|
||||
GetUrl {
|
||||
url: &'a str,
|
||||
target: &'a str,
|
||||
url: SwfStr<'a>,
|
||||
target: SwfStr<'a>,
|
||||
},
|
||||
GetUrl2 {
|
||||
send_vars_method: SendVarsMethod,
|
||||
|
@ -53,7 +55,7 @@ pub enum Action<'a> {
|
|||
set_playing: bool,
|
||||
scene_offset: u16,
|
||||
},
|
||||
GotoLabel(&'a str),
|
||||
GotoLabel(SwfStr<'a>),
|
||||
Greater,
|
||||
If {
|
||||
offset: i16,
|
||||
|
@ -89,7 +91,7 @@ pub enum Action<'a> {
|
|||
Return,
|
||||
SetMember,
|
||||
SetProperty,
|
||||
SetTarget(&'a str),
|
||||
SetTarget(SwfStr<'a>),
|
||||
SetTarget2,
|
||||
SetVariable,
|
||||
StackSwap,
|
||||
|
@ -138,7 +140,7 @@ pub enum Value<'a> {
|
|||
Int(i32),
|
||||
Float(f32),
|
||||
Double(f64),
|
||||
Str(&'a str),
|
||||
Str(SwfStr<'a>),
|
||||
Register(u8),
|
||||
ConstantPool(u16),
|
||||
}
|
||||
|
@ -152,7 +154,7 @@ pub enum SendVarsMethod {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Function<'a> {
|
||||
pub name: &'a str,
|
||||
pub name: SwfStr<'a>,
|
||||
pub register_count: u8,
|
||||
pub params: Vec<FunctionParam<'a>>,
|
||||
pub preload_parent: bool,
|
||||
|
@ -169,7 +171,7 @@ pub struct Function<'a> {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct FunctionParam<'a> {
|
||||
pub name: &'a str,
|
||||
pub name: SwfStr<'a>,
|
||||
pub register_index: Option<u8>,
|
||||
}
|
||||
|
||||
|
@ -182,6 +184,6 @@ pub struct TryBlock<'a> {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum CatchVar<'a> {
|
||||
Var(&'a str),
|
||||
Var(SwfStr<'a>),
|
||||
Register(u8),
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ impl<W: Write> Writer<W> {
|
|||
self.write_action_header(OpCode::ConstantPool, len)?;
|
||||
self.write_u16(constants.len() as u16)?;
|
||||
for constant in constants {
|
||||
self.write_c_string(constant)?;
|
||||
self.write_string(*constant)?;
|
||||
}
|
||||
}
|
||||
Action::Decrement => self.write_action_header(OpCode::Decrement, 0)?,
|
||||
|
@ -60,10 +60,10 @@ impl<W: Write> Writer<W> {
|
|||
let len =
|
||||
name.len() + 1 + 2 + params.iter().map(|p| p.len() + 1).sum::<usize>() + 2;
|
||||
self.write_action_header(OpCode::DefineFunction, len)?;
|
||||
self.write_c_string(name)?;
|
||||
self.write_string(*name)?;
|
||||
self.write_u16(params.len() as u16)?;
|
||||
for param in params {
|
||||
self.write_c_string(param)?;
|
||||
self.write_string(*param)?;
|
||||
}
|
||||
self.write_u16(actions.len() as u16)?;
|
||||
self.inner.write_all(actions)?;
|
||||
|
@ -79,7 +79,7 @@ impl<W: Write> Writer<W> {
|
|||
.sum::<usize>()
|
||||
+ 4;
|
||||
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_u8(function.register_count)?;
|
||||
let flags = if function.preload_global {
|
||||
|
@ -108,7 +108,7 @@ impl<W: Write> Writer<W> {
|
|||
} else {
|
||||
0
|
||||
})?;
|
||||
self.write_c_string(¶m.name)?;
|
||||
self.write_string(param.name)?;
|
||||
}
|
||||
self.write_u16(function.actions.len() as u16)?;
|
||||
self.inner.write_all(&function.actions)?;
|
||||
|
@ -132,8 +132,8 @@ impl<W: Write> Writer<W> {
|
|||
ref target,
|
||||
} => {
|
||||
self.write_action_header(OpCode::GetUrl, url.len() + target.len() + 2)?;
|
||||
self.write_c_string(url)?;
|
||||
self.write_c_string(target)?;
|
||||
self.write_string(*url)?;
|
||||
self.write_string(*target)?;
|
||||
}
|
||||
Action::GetUrl2 {
|
||||
send_vars_method,
|
||||
|
@ -169,7 +169,7 @@ impl<W: Write> Writer<W> {
|
|||
}
|
||||
Action::GotoLabel(ref label) => {
|
||||
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::If { offset } => {
|
||||
|
@ -232,7 +232,7 @@ impl<W: Write> Writer<W> {
|
|||
Action::SetProperty => self.write_action_header(OpCode::SetProperty, 0)?,
|
||||
Action::SetTarget(ref target) => {
|
||||
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::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(finally_length as u16)?;
|
||||
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)?,
|
||||
_ => (),
|
||||
}
|
||||
|
@ -352,9 +352,9 @@ impl<W: Write> Writer<W> {
|
|||
|
||||
fn write_push_value(&mut self, value: &Value) -> Result<()> {
|
||||
match *value {
|
||||
Value::Str(ref string) => {
|
||||
Value::Str(string) => {
|
||||
self.write_u8(0)?;
|
||||
self.write_c_string(string)?;
|
||||
self.write_string(string)?;
|
||||
}
|
||||
Value::Float(v) => {
|
||||
self.write_u8(1)?;
|
||||
|
|
|
@ -21,6 +21,7 @@ pub mod avm1;
|
|||
pub mod avm2;
|
||||
pub mod error;
|
||||
pub mod read;
|
||||
mod string;
|
||||
mod tag_code;
|
||||
mod types;
|
||||
pub mod write;
|
||||
|
@ -30,6 +31,7 @@ mod test_data;
|
|||
|
||||
/// Reexports
|
||||
pub use read::{read_swf, read_swf_header};
|
||||
pub use string::*;
|
||||
pub use tag_code::TagCode;
|
||||
pub use types::*;
|
||||
pub use write::write_swf;
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
clippy::unreadable_literal
|
||||
)]
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::types::*;
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
string::SwfStr,
|
||||
types::*,
|
||||
};
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use enumset::EnumSet;
|
||||
use std::collections::HashSet;
|
||||
|
@ -246,20 +249,6 @@ pub trait SwfRead<R: Read> {
|
|||
num.swap(3, 7);
|
||||
(&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> {
|
||||
|
@ -324,6 +313,7 @@ impl<'a, 'b> BitReader<'a, 'b> {
|
|||
pub struct Reader<'a> {
|
||||
input: &'a [u8],
|
||||
version: u8,
|
||||
encoding: &'static encoding_rs::Encoding,
|
||||
}
|
||||
|
||||
impl<'a> SwfRead<&'a [u8]> for Reader<'a> {
|
||||
|
@ -366,7 +356,16 @@ impl<'a> SwfRead<&'a [u8]> for Reader<'a> {
|
|||
|
||||
impl<'a> 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 {
|
||||
|
@ -378,6 +377,13 @@ impl<'a> Reader<'a> {
|
|||
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> {
|
||||
BitReader {
|
||||
input: self.get_inner(),
|
||||
|
@ -406,7 +412,7 @@ impl<'a> Reader<'a> {
|
|||
slice
|
||||
}
|
||||
|
||||
fn read_string(&mut self) -> io::Result<SwfStr<'a>> {
|
||||
pub fn read_string(&mut self) -> io::Result<SwfStr<'a>> {
|
||||
let mut pos = 0;
|
||||
loop {
|
||||
let byte = *self.input.get(pos).ok_or(io::Error::new(
|
||||
|
@ -418,26 +424,17 @@ impl<'a> Reader<'a> {
|
|||
}
|
||||
pos += 1;
|
||||
}
|
||||
// TODO: There is probably a better way to do this.
|
||||
// TODO: Verify ANSI for SWF 5 and earlier.
|
||||
let s = unsafe { std::str::from_utf8_unchecked(&self.input.get_unchecked(..pos)) };
|
||||
|
||||
let s = unsafe {
|
||||
let slice = self.input.get_unchecked(..pos);
|
||||
SwfStr::from_bytes_unchecked(slice, self.encoding)
|
||||
};
|
||||
self.input = &self.input[pos + 1..];
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn read_string_with_len(&mut self, len: usize) -> io::Result<SwfStr<'a>> {
|
||||
// TODO: There is probably a better way to do this.
|
||||
// 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
|
||||
Ok(SwfStr::from_bytes(&self.read_slice(len)?, self.encoding))
|
||||
}
|
||||
|
||||
/// 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::avm2::read::tests::read_abc_from_file;
|
||||
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::{read_swf, read_swf_header};
|
||||
use crate::string::SwfStr;
|
||||
use crate::tag_code::TagCode;
|
||||
use crate::types::*;
|
||||
use crate::write::write_swf;
|
||||
use encoding_rs::WINDOWS_1252;
|
||||
use std::fs::File;
|
||||
use std::vec::Vec;
|
||||
|
||||
|
@ -292,7 +294,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
},
|
||||
ButtonAction {
|
||||
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");
|
||||
},
|
||||
],
|
||||
|
@ -400,8 +402,8 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
indent: Twips::from_pixels(1.0),
|
||||
leading: Twips::from_pixels(2.0),
|
||||
}),
|
||||
variable_name: "foo",
|
||||
initial_text: Some("-_-"),
|
||||
variable_name: SwfStr::from_str_with_encoding("foo", WINDOWS_1252).unwrap(),
|
||||
initial_text: Some(SwfStr::from_str_with_encoding("-_-", WINDOWS_1252).unwrap()),
|
||||
is_word_wrap: false,
|
||||
is_multiline: true,
|
||||
is_password: false,
|
||||
|
@ -670,7 +672,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
10,
|
||||
Tag::DefineFont4(Font4 {
|
||||
id: 1,
|
||||
name: "Dummy",
|
||||
name: "Dummy".into(),
|
||||
is_italic: false,
|
||||
is_bold: false,
|
||||
data: None,
|
||||
|
@ -682,7 +684,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
Tag::DefineFontInfo(Box::new(FontInfo {
|
||||
id: 1,
|
||||
version: 1,
|
||||
name: "Verdana",
|
||||
name: SwfStr::from_str_with_encoding("Verdana", WINDOWS_1252).unwrap(),
|
||||
is_small_text: false,
|
||||
is_ansi: true,
|
||||
is_shift_jis: false,
|
||||
|
@ -698,7 +700,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
Tag::DefineFontInfo(Box::new(FontInfo {
|
||||
id: 1,
|
||||
version: 2,
|
||||
name: "Verdana",
|
||||
name: "Verdana".into(),
|
||||
is_small_text: false,
|
||||
is_ansi: true,
|
||||
is_shift_jis: false,
|
||||
|
@ -713,8 +715,8 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
9,
|
||||
Tag::DefineFontName {
|
||||
id: 2,
|
||||
name: "Dummy",
|
||||
copyright_info: "Dummy font for swf-rs tests",
|
||||
name: "Dummy".into(),
|
||||
copyright_info: "Dummy font for swf-rs tests".into(),
|
||||
},
|
||||
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 {
|
||||
scenes: vec![
|
||||
FrameLabelData {
|
||||
frame_num: 0,
|
||||
label: "Scene 1",
|
||||
label: "Scene 1".into(),
|
||||
},
|
||||
FrameLabelData {
|
||||
frame_num: 25,
|
||||
label: "Scene2Scene2Scene2Scene2Scene2",
|
||||
label: "Scene2Scene2Scene2Scene2Scene2".into(),
|
||||
},
|
||||
FrameLabelData {
|
||||
frame_num: 26,
|
||||
label: "test日本語test",
|
||||
label: "test日本語test".into(),
|
||||
},
|
||||
],
|
||||
frame_labels: vec![
|
||||
FrameLabelData {
|
||||
frame_num: 0,
|
||||
label: "a",
|
||||
label: "a".into(),
|
||||
},
|
||||
FrameLabelData {
|
||||
frame_num: 9,
|
||||
label: "b",
|
||||
label: "b".into(),
|
||||
},
|
||||
FrameLabelData {
|
||||
frame_num: 17,
|
||||
label: "❤😁aaa",
|
||||
label: "❤😁aaa".into(),
|
||||
},
|
||||
FrameLabelData {
|
||||
frame_num: 25,
|
||||
label: "frameInScene2",
|
||||
label: "frameInScene2".into(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
@ -1954,7 +1956,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
),
|
||||
(
|
||||
6,
|
||||
Tag::EnableDebugger("$1$ve$EG3LE6bumvJ2pR8F5qXny/"),
|
||||
Tag::EnableDebugger("$1$ve$EG3LE6bumvJ2pR8F5qXny/".into()),
|
||||
read_tag_bytes_from_file(
|
||||
"tests/swfs/EnableDebugger2-CS6.swf",
|
||||
TagCode::EnableDebugger2,
|
||||
|
@ -1962,9 +1964,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
),
|
||||
(
|
||||
10,
|
||||
Tag::EnableTelemetry {
|
||||
password_hash: &[],
|
||||
},
|
||||
Tag::EnableTelemetry { password_hash: &[] },
|
||||
read_tag_bytes_from_file("tests/swfs/EnableTelemetry.swf", TagCode::EnableTelemetry),
|
||||
),
|
||||
(
|
||||
|
@ -1984,7 +1984,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
6,
|
||||
Tag::ExportAssets(vec![ExportedAsset {
|
||||
id: 2,
|
||||
name: "Test💯",
|
||||
name: "Test💯".into(),
|
||||
}]),
|
||||
read_tag_bytes_from_file("tests/swfs/ExportAssets-CS6.swf", TagCode::ExportAssets),
|
||||
),
|
||||
|
@ -2002,7 +2002,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
(
|
||||
3,
|
||||
Tag::FrameLabel(FrameLabel {
|
||||
label: "test",
|
||||
label: SwfStr::from_str_with_encoding("test", WINDOWS_1252).unwrap(),
|
||||
is_anchor: false,
|
||||
}),
|
||||
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.
|
||||
Tag::FrameLabel(FrameLabel {
|
||||
label: "anchor_tag",
|
||||
label: "anchor_tag".into(),
|
||||
is_anchor: true,
|
||||
}),
|
||||
read_tag_bytes_from_file_with_index(
|
||||
|
@ -2026,10 +2026,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
(
|
||||
7,
|
||||
Tag::ImportAssets {
|
||||
url: "ExportAssets-CS6.swf",
|
||||
url: "ExportAssets-CS6.swf".into(),
|
||||
imports: vec![ExportedAsset {
|
||||
id: 1,
|
||||
name: "Test💯",
|
||||
name: "Test💯".into(),
|
||||
}],
|
||||
},
|
||||
read_tag_bytes_from_file("tests/swfs/ImportAssets-CS6.swf", TagCode::ImportAssets),
|
||||
|
@ -2037,10 +2037,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
(
|
||||
8,
|
||||
Tag::ImportAssets {
|
||||
url: "ExportAssets-CS6.swf",
|
||||
url: "ExportAssets-CS6.swf".into(),
|
||||
imports: vec![ExportedAsset {
|
||||
id: 1,
|
||||
name: "Test💯",
|
||||
name: "Test💯".into(),
|
||||
}],
|
||||
},
|
||||
read_tag_bytes_from_file("tests/swfs/ImportAssets2-CS6.swf", TagCode::ImportAssets2),
|
||||
|
@ -2084,7 +2084,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
),
|
||||
(
|
||||
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],
|
||||
),
|
||||
(
|
||||
|
@ -2237,7 +2237,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
b_add: 20,
|
||||
}),
|
||||
ratio: None,
|
||||
name: Some("test"),
|
||||
name: Some("test".into()),
|
||||
clip_depth: None,
|
||||
class_name: None,
|
||||
filters: Some(vec![
|
||||
|
@ -2388,7 +2388,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
),
|
||||
(
|
||||
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),
|
||||
),
|
||||
(
|
||||
|
@ -2443,11 +2446,11 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
Tag::SymbolClass(vec![
|
||||
SymbolClassLink {
|
||||
id: 2,
|
||||
class_name: "foo.Test",
|
||||
class_name: "foo.Test".into(),
|
||||
},
|
||||
SymbolClassLink {
|
||||
id: 0,
|
||||
class_name: "DocumentTest",
|
||||
class_name: "DocumentTest".into(),
|
||||
},
|
||||
]),
|
||||
read_tag_bytes_from_file("tests/swfs/SymbolClass.swf", TagCode::SymbolClass),
|
||||
|
@ -2469,7 +2472,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
|
|||
(
|
||||
9,
|
||||
Tag::StartSound2 {
|
||||
class_name: "TestSound",
|
||||
class_name: "TestSound".into(),
|
||||
sound_info: Box::new(SoundInfo {
|
||||
event: SoundEvent::Event,
|
||||
in_sample: None,
|
||||
|
@ -2670,8 +2673,8 @@ pub fn avm1_tests() -> Vec<Avm1TestData> {
|
|||
(
|
||||
3,
|
||||
Action::GetUrl {
|
||||
url: "a",
|
||||
target: "b",
|
||||
url: SwfStr::from_str_with_encoding("a", WINDOWS_1252).unwrap(),
|
||||
target: SwfStr::from_str_with_encoding("b", WINDOWS_1252).unwrap(),
|
||||
},
|
||||
vec![0x83, 4, 0, 97, 0, 98, 0],
|
||||
),
|
||||
|
@ -2713,7 +2716,7 @@ pub fn avm1_tests() -> Vec<Avm1TestData> {
|
|||
),
|
||||
(
|
||||
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],
|
||||
),
|
||||
(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]),
|
||||
(
|
||||
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],
|
||||
),
|
||||
(
|
||||
|
@ -2796,7 +2801,7 @@ pub fn avm1_tests() -> Vec<Avm1TestData> {
|
|||
(4, Action::RandomNumber, vec![0x30]),
|
||||
(
|
||||
3,
|
||||
Action::SetTarget("test"),
|
||||
Action::SetTarget(SwfStr::from_str_with_encoding("test", WINDOWS_1252).unwrap()),
|
||||
vec![0x8B, 5, 0, 116, 101, 115, 116, 0],
|
||||
),
|
||||
(4, Action::SetVariable, vec![0x1D]),
|
||||
|
@ -2845,8 +2850,11 @@ pub fn avm1_tests() -> Vec<Avm1TestData> {
|
|||
(
|
||||
5,
|
||||
Action::DefineFunction {
|
||||
name: "cliche",
|
||||
params: vec!["greeting", "name"],
|
||||
name: SwfStr::from_str_with_encoding("cliche", WINDOWS_1252).unwrap(),
|
||||
params: vec![
|
||||
SwfStr::from_str_with_encoding("greeting", WINDOWS_1252).unwrap(),
|
||||
SwfStr::from_str_with_encoding("name", WINDOWS_1252).unwrap(),
|
||||
],
|
||||
actions: &[
|
||||
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,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//! These structures are documented in the Adobe SWF File Format Specification
|
||||
//! version 19 (henceforth SWF19):
|
||||
//! https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf
|
||||
use crate::string::SwfStr;
|
||||
use enumset::{EnumSet, EnumSetType};
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
@ -10,8 +11,6 @@ mod matrix;
|
|||
|
||||
pub use matrix::Matrix;
|
||||
|
||||
pub type SwfStr<'a> = &'a str;
|
||||
|
||||
/// A complete header and tags in the SWF file.
|
||||
/// This is returned by the `swf::read_swf` convenience method.
|
||||
#[derive(Debug, PartialEq)]
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
clippy::unreadable_literal
|
||||
)]
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::tag_code::TagCode;
|
||||
use crate::types::*;
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
string::SwfStr,
|
||||
tag_code::TagCode,
|
||||
types::*,
|
||||
};
|
||||
use byteorder::{LittleEndian, WriteBytesExt};
|
||||
use enumset::EnumSet;
|
||||
use std::cmp::max;
|
||||
|
@ -185,7 +188,7 @@ pub trait SwfWrite<W: Write> {
|
|||
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.write_u8(0)
|
||||
}
|
||||
|
@ -306,8 +309,8 @@ impl<W: Write> SwfWrite<W> for Writer<W> {
|
|||
self.output.write_f64::<LittleEndian>(n)
|
||||
}
|
||||
|
||||
fn write_c_string(&mut self, s: &str) -> io::Result<()> {
|
||||
self.get_inner().write_all(s.as_bytes())?;
|
||||
fn write_string(&mut self, s: SwfStr<'_>) -> io::Result<()> {
|
||||
self.output.write_all(s.as_bytes())?;
|
||||
self.write_u8(0)
|
||||
}
|
||||
}
|
||||
|
@ -534,11 +537,11 @@ impl<W: Write> Writer<W> {
|
|||
|
||||
Tag::ExportAssets(ref exports) => self.write_export_assets(&exports[..])?,
|
||||
|
||||
Tag::Protect(ref password) => {
|
||||
if let Some(ref password_md5) = *password {
|
||||
Tag::Protect(password) => {
|
||||
if let Some(password_md5) = password {
|
||||
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_c_string(password_md5)?;
|
||||
self.write_string(password_md5)?;
|
||||
} else {
|
||||
self.write_tag_header(TagCode::Protect, 0)?;
|
||||
}
|
||||
|
@ -782,14 +785,14 @@ impl<W: Write> Writer<W> {
|
|||
|
||||
Tag::DefineFontName {
|
||||
id,
|
||||
ref name,
|
||||
ref copyright_info,
|
||||
name,
|
||||
copyright_info,
|
||||
} => {
|
||||
let len = name.len() + copyright_info.len() + 4;
|
||||
self.write_tag_header(TagCode::DefineFontName, len as u32)?;
|
||||
self.write_character_id(id)?;
|
||||
self.write_c_string(name)?;
|
||||
self.write_c_string(copyright_info)?;
|
||||
self.write_string(name)?;
|
||||
self.write_string(copyright_info)?;
|
||||
}
|
||||
|
||||
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;
|
||||
self.write_tag_header(TagCode::DoAbc, len as u32)?;
|
||||
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)?;
|
||||
}
|
||||
Tag::DoAction(ref action_data) => {
|
||||
|
@ -835,7 +838,7 @@ impl<W: Write> Writer<W> {
|
|||
self.output.write_all(action_data)?;
|
||||
}
|
||||
|
||||
Tag::EnableDebugger(ref password_md5) => {
|
||||
Tag::EnableDebugger(password_md5) => {
|
||||
let len = password_md5.len() as u32 + 1;
|
||||
if self.version >= 6 {
|
||||
// SWF v6+ uses EnableDebugger2 tag.
|
||||
|
@ -845,7 +848,7 @@ impl<W: Write> Writer<W> {
|
|||
self.write_tag_header(TagCode::EnableDebugger, len)?;
|
||||
}
|
||||
|
||||
self.write_c_string(password_md5)?;
|
||||
self.write_string(password_md5)?;
|
||||
}
|
||||
|
||||
Tag::EnableTelemetry { ref password_hash } => {
|
||||
|
@ -861,10 +864,7 @@ impl<W: Write> Writer<W> {
|
|||
|
||||
Tag::End => self.write_tag_header(TagCode::End, 0)?,
|
||||
|
||||
Tag::ImportAssets {
|
||||
ref url,
|
||||
ref imports,
|
||||
} => {
|
||||
Tag::ImportAssets { url, ref imports } => {
|
||||
let len = imports.iter().map(|e| e.name.len() as u32 + 3).sum::<u32>()
|
||||
+ url.len() as u32
|
||||
+ 1
|
||||
|
@ -872,17 +872,17 @@ impl<W: Write> Writer<W> {
|
|||
// SWF v8 and later use ImportAssets2 tag.
|
||||
if self.version >= 8 {
|
||||
self.write_tag_header(TagCode::ImportAssets2, len + 2)?;
|
||||
self.write_c_string(url)?;
|
||||
self.write_string(url)?;
|
||||
self.write_u8(1)?;
|
||||
self.write_u8(0)?;
|
||||
} else {
|
||||
self.write_tag_header(TagCode::ImportAssets, len)?;
|
||||
self.write_c_string(url)?;
|
||||
self.write_string(url)?;
|
||||
}
|
||||
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_c_string(name)?;
|
||||
self.write_string(name)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -891,9 +891,9 @@ impl<W: Write> Writer<W> {
|
|||
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_c_string(metadata)?;
|
||||
self.write_string(metadata)?;
|
||||
}
|
||||
|
||||
// TODO: Allow clone of color.
|
||||
|
@ -969,7 +969,7 @@ impl<W: Write> Writer<W> {
|
|||
}
|
||||
|
||||
Tag::StartSound2 {
|
||||
ref class_name,
|
||||
class_name,
|
||||
ref sound_info,
|
||||
} => {
|
||||
let length = class_name.len() as u32
|
||||
|
@ -987,7 +987,7 @@ impl<W: Write> Writer<W> {
|
|||
0
|
||||
};
|
||||
self.write_tag_header(TagCode::StartSound2, length)?;
|
||||
self.write_c_string(class_name)?;
|
||||
self.write_string(class_name)?;
|
||||
self.write_sound_info(sound_info)?;
|
||||
}
|
||||
|
||||
|
@ -999,9 +999,9 @@ impl<W: Write> Writer<W> {
|
|||
+ 2;
|
||||
self.write_tag_header(TagCode::SymbolClass, len)?;
|
||||
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_c_string(class_name)?;
|
||||
self.write_string(class_name)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1033,15 +1033,12 @@ impl<W: Write> Writer<W> {
|
|||
self.write_u32(flags)?;
|
||||
}
|
||||
|
||||
Tag::FrameLabel(FrameLabel {
|
||||
ref label,
|
||||
is_anchor,
|
||||
}) => {
|
||||
Tag::FrameLabel(FrameLabel { label, is_anchor }) => {
|
||||
// TODO: Assert proper version
|
||||
let is_anchor = is_anchor && self.version >= 6;
|
||||
let length = label.len() as u32 + if is_anchor { 2 } else { 1 };
|
||||
self.write_tag_header(TagCode::FrameLabel, length)?;
|
||||
self.write_c_string(label)?;
|
||||
self.write_string(label)?;
|
||||
if is_anchor {
|
||||
self.write_u8(1)?;
|
||||
}
|
||||
|
@ -1506,12 +1503,12 @@ impl<W: Write> Writer<W> {
|
|||
writer.write_encoded_u32(data.scenes.len() as u32)?;
|
||||
for scene in &data.scenes {
|
||||
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)?;
|
||||
for frame_label in &data.frame_labels {
|
||||
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)?;
|
||||
|
@ -1596,9 +1593,9 @@ impl<W: Write> Writer<W> {
|
|||
+ 2;
|
||||
self.write_tag_header(TagCode::ExportAssets, len)?;
|
||||
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_c_string(name)?;
|
||||
self.write_string(name)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2035,8 +2032,8 @@ impl<W: Write> Writer<W> {
|
|||
writer.write_u16(place_object.depth)?;
|
||||
|
||||
if place_object_version >= 3 {
|
||||
if let Some(ref class_name) = place_object.class_name {
|
||||
writer.write_c_string(class_name)?;
|
||||
if let Some(class_name) = place_object.class_name {
|
||||
writer.write_string(class_name)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2054,8 +2051,8 @@ impl<W: Write> Writer<W> {
|
|||
if let Some(ratio) = place_object.ratio {
|
||||
writer.write_u16(ratio)?;
|
||||
}
|
||||
if let Some(ref name) = place_object.name {
|
||||
writer.write_c_string(name)?;
|
||||
if let Some(name) = place_object.name {
|
||||
writer.write_string(name)?;
|
||||
};
|
||||
if let Some(clip_depth) = place_object.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_bold { 0b1 } else { 0 },
|
||||
)?;
|
||||
self.write_c_string(&font.name)?;
|
||||
self.write_string(font.name)?;
|
||||
if let Some(ref data) = font.data {
|
||||
self.output.write_all(data)?;
|
||||
}
|
||||
|
@ -2647,8 +2644,8 @@ impl<W: Write> Writer<W> {
|
|||
}
|
||||
|
||||
// TODO(Herschel): Check SWF version.
|
||||
if let Some(ref class) = edit_text.font_class_name {
|
||||
writer.write_c_string(class)?;
|
||||
if let Some(class) = edit_text.font_class_name {
|
||||
writer.write_string(class)?;
|
||||
}
|
||||
|
||||
// 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_c_string(&edit_text.variable_name)?;
|
||||
if let Some(ref text) = edit_text.initial_text {
|
||||
writer.write_c_string(text)?;
|
||||
writer.write_string(edit_text.variable_name)?;
|
||||
if let Some(text) = edit_text.initial_text {
|
||||
writer.write_string(text)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2993,7 +2990,7 @@ mod tests {
|
|||
{
|
||||
// TODO: What if I use a cursor instead of buf ?
|
||||
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<_>>());
|
||||
}
|
||||
|
@ -3003,7 +3000,7 @@ mod tests {
|
|||
{
|
||||
// TODO: What if I use a cursor instead of buf ?
|
||||
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<_>>());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue