swf: Add HeaderExt struct providing additional info

When reading an SWF, search for FileAttributes and
SetBackgroundColor and return this along with the header data
because it's useful (in particular, the AS3 flag).
This commit is contained in:
Mike Welsh 2021-05-22 17:19:45 -07:00
parent aa7dc50385
commit e1439f4105
19 changed files with 283 additions and 149 deletions

View File

@ -769,7 +769,7 @@ fn get_bytes_loaded<'gc>(
let bytes_loaded = if movie_clip.is_root() {
movie_clip
.movie()
.map(|mv| mv.header().uncompressed_length)
.map(|mv| mv.header().uncompressed_len())
.unwrap_or_default()
} else {
movie_clip.tag_stream_len() as u32
@ -787,7 +787,7 @@ fn get_bytes_total<'gc>(
let bytes_total = if movie_clip.is_root() {
movie_clip
.movie()
.map(|mv| mv.header().uncompressed_length)
.map(|mv| mv.header().uncompressed_len())
.unwrap_or_default()
} else {
movie_clip.tag_stream_len() as u32

View File

@ -114,7 +114,7 @@ pub fn get_progress<'gc>(
"bytesLoaded",
movieclip
.movie()
.map(|mv| (mv.header().uncompressed_length).into())
.map(|mv| (mv.header().uncompressed_len()).into())
.unwrap_or(Value::Undefined),
Attribute::empty(),
);
@ -123,7 +123,7 @@ pub fn get_progress<'gc>(
"bytesTotal",
movieclip
.movie()
.map(|mv| (mv.header().uncompressed_length).into())
.map(|mv| (mv.header().uncompressed_len()).into())
.unwrap_or(Value::Undefined),
Attribute::empty(),
);

View File

@ -37,7 +37,7 @@ impl<'gc> Timers<'gc> {
return None;
}
let version = context.swf.header().version;
let version = context.swf.version();
let globals = context.avm1.global_object_cell();
let level0 = context.stage.root_clip();

View File

@ -13,7 +13,7 @@ use crate::avm2::value::Value;
use crate::avm2::{AvmString, Error};
use crate::display_object::TDisplayObject;
use gc_arena::{GcCell, MutationContext};
use swf::{write_swf, Compression, Swf};
use swf::{write_swf, Compression};
/// Implements `flash.display.LoaderInfo`'s instance constructor.
pub fn instance_init<'gc>(
@ -173,7 +173,7 @@ pub fn frame_rate<'gc>(
return Err("Error: The stage's loader info does not have a frame rate".into())
}
LoaderStream::Swf(root, _) => {
return Ok(root.header().frame_rate.into());
return Ok(root.header().frame_rate().into());
}
}
}
@ -195,8 +195,8 @@ pub fn height<'gc>(
return Err("Error: The stage's loader info does not have a height".into())
}
LoaderStream::Swf(root, _) => {
let y_min = root.header().stage_size.y_min;
let y_max = root.header().stage_size.y_max;
let y_min = root.header().stage_size().y_min;
let y_max = root.header().stage_size().y_max;
return Ok((y_max - y_min).to_pixels().into());
}
}
@ -228,7 +228,7 @@ pub fn swf_version<'gc>(
return Err("Error: The stage's loader info does not have a SWF version".into())
}
LoaderStream::Swf(root, _) => {
return Ok(root.header().version.into());
return Ok(root.version().into());
}
}
}
@ -273,8 +273,8 @@ pub fn width<'gc>(
return Err("Error: The stage's loader info does not have a width".into())
}
LoaderStream::Swf(root, _) => {
let x_min = root.header().stage_size.x_min;
let x_max = root.header().stage_size.x_max;
let x_min = root.header().stage_size().x_min;
let x_max = root.header().stage_size().x_max;
return Ok((x_max - x_min).to_pixels().into());
}
}
@ -304,18 +304,10 @@ pub fn bytes<'gc>(
// First, write a fake header corresponding to an
// uncompressed SWF
let mut header = root.header().clone();
let mut header = root.header().swf_header().clone();
header.compression = Compression::None;
header.uncompressed_length = root.data().len() as u32;
write_swf(
&Swf {
header,
tags: vec![],
},
&mut *ba_write,
)
.unwrap();
write_swf(&header, &[], &mut *ba_write).unwrap();
// `swf` always writes an implicit end tag, let's cut that
// off. We scroll back 2 bytes before writing the actual

View File

@ -964,7 +964,7 @@ impl<'gc> EditText<'gc> {
activation.run_with_child_frame_for_display_object(
"[Text Field Binding]",
parent,
activation.context.swf.header().version,
activation.context.swf.version(),
|activation| {
if let Ok(Some((object, property))) =
activation.resolve_variable_path(parent, &variable)
@ -1053,7 +1053,7 @@ impl<'gc> EditText<'gc> {
activation.run_with_child_frame_for_display_object(
"[Propagate Text Binding]",
self.avm1_parent().unwrap(),
activation.context.swf.header().version,
activation.context.swf.version(),
|activation| {
let _ = object.set(
property,
@ -1204,7 +1204,7 @@ impl<'gc> EditText<'gc> {
if changed {
let globals = context.avm1.global_object_cell();
let swf_version = context.swf.header().version;
let swf_version = context.swf.version();
let mut activation = Avm1Activation::from_nothing(
context.reborrow(),
ActivationIdentifier::root("[Propagate Text Binding]"),

View File

@ -185,7 +185,7 @@ impl<'gc> MovieClip<'gc> {
/// Construct a movie clip that represents an entire movie.
pub fn from_movie(gc_context: MutationContext<'gc, '_>, movie: Arc<SwfMovie>) -> Self {
let num_frames = movie.header().num_frames;
let num_frames = movie.header().num_frames();
let mc = MovieClip(GcCell::allocate(
gc_context,
MovieClipData {
@ -525,12 +525,7 @@ impl<'gc> MovieClip<'gc> {
)
})?;
Avm1::run_stack_frame_for_init_action(
self.into(),
context.swf.header().version,
slice,
context,
);
Avm1::run_stack_frame_for_init_action(self.into(), context.swf.version(), slice, context);
Ok(())
}
@ -2114,7 +2109,7 @@ impl<'gc> MovieClipData<'gc> {
) {
let is_swf = movie.is_some();
let movie = movie.unwrap_or_else(|| Arc::new(SwfMovie::empty(self.movie().version())));
let total_frames = movie.header().num_frames;
let total_frames = movie.header().num_frames();
self.base.reset_for_movie_load();
self.static_data = Gc::allocate(

View File

@ -417,7 +417,7 @@ impl<'gc> Library<'gc> {
if !self.movie_libraries.contains_key(&movie) {
let slice = SwfSlice::from(movie.clone());
let mut reader = slice.read_from(0);
let movie_version = movie.header().version;
let movie_version = movie.version();
let vm_type = if movie_version > 8 {
match reader.read_tag_code_and_length() {
Ok((tag_code, _tag_len))

View File

@ -127,7 +127,7 @@ impl<'gc> LoadManager<'gc> {
fetch: OwnedFuture<Vec<u8>, Error>,
url: String,
parameters: Vec<(String, String)>,
on_metadata: Box<dyn FnOnce(&swf::Header)>,
on_metadata: Box<dyn FnOnce(&swf::HeaderExt)>,
) -> OwnedFuture<(), Error> {
let loader = Loader::RootMovie { self_handle: None };
let handle = self.add_loader(loader);
@ -366,7 +366,7 @@ impl<'gc> Loader<'gc> {
fetch: OwnedFuture<Vec<u8>, Error>,
mut url: String,
parameters: Vec<(String, String)>,
on_metadata: Box<dyn FnOnce(&swf::Header)>,
on_metadata: Box<dyn FnOnce(&swf::HeaderExt)>,
) -> OwnedFuture<(), Error> {
let _handle = match self {
Loader::RootMovie { self_handle, .. } => {

View File

@ -346,7 +346,7 @@ impl Player {
&mut self,
movie_url: &str,
parameters: Vec<(String, String)>,
on_metadata: Box<dyn FnOnce(&swf::Header)>,
on_metadata: Box<dyn FnOnce(&swf::HeaderExt)>,
) {
self.mutate_with_update_context(|context| {
let fetch = context.navigator.fetch(movie_url, RequestOptions::get());
@ -370,12 +370,12 @@ impl Player {
pub fn set_root_movie(&mut self, movie: Arc<SwfMovie>) {
info!(
"Loaded SWF version {}, with a resolution of {}x{}",
movie.header().version,
movie.header().stage_size.x_max,
movie.header().stage_size.y_max
movie.version(),
movie.header().stage_size().x_max,
movie.header().stage_size().y_max
);
self.frame_rate = movie.header().frame_rate.into();
self.frame_rate = movie.header().frame_rate().into();
self.swf = movie;
self.instance_counter = 0;
@ -626,7 +626,7 @@ impl Player {
callback: Object<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) {
let version = context.swf.header().version;
let version = context.swf.version();
let globals = context.avm1.global_object_cell();
let root_clip = context.stage.root_clip();
@ -1167,7 +1167,7 @@ impl Player {
Avm1::run_stack_frame_for_action(
actions.clip,
"[Frame]",
context.swf.header().version,
context.swf.version(),
bytecode,
context,
);
@ -1194,7 +1194,7 @@ impl Player {
let _ = activation.run_child_frame_for_action(
"[Actions]",
actions.clip,
activation.context.swf.header().version,
activation.context.swf.version(),
event,
);
}
@ -1212,7 +1212,7 @@ impl Player {
Avm1::run_stack_frame_for_action(
actions.clip,
"[Construct]",
context.swf.header().version,
context.swf.version(),
event,
context,
);
@ -1223,7 +1223,7 @@ impl Player {
Avm1::run_stack_frame_for_method(
actions.clip,
object,
context.swf.header().version,
context.swf.version(),
context,
name,
&args,

View File

@ -2,7 +2,7 @@ use crate::backend::navigator::url_from_relative_path;
use gc_arena::Collect;
use std::path::Path;
use std::sync::Arc;
use swf::{Header, TagCode};
use swf::{HeaderExt, TagCode};
pub type Error = Box<dyn std::error::Error>;
pub type DecodeResult = Result<(), Error>;
@ -14,7 +14,7 @@ pub type SwfStream<'a> = swf::read::Reader<'a>;
#[collect(require_static)]
pub struct SwfMovie {
/// The SWF header parsed from the data stream.
header: Header,
header: HeaderExt,
/// Uncompressed SWF data.
data: Vec<u8>,
@ -40,14 +40,7 @@ impl SwfMovie {
/// Construct an empty movie.
pub fn empty(swf_version: u8) -> Self {
Self {
header: Header {
compression: swf::Compression::None,
version: swf_version,
uncompressed_length: 0,
stage_size: swf::Rectangle::default(),
frame_rate: 1.0,
num_frames: 0,
},
header: HeaderExt::default_with_swf_version(swf_version),
data: vec![],
url: None,
loader_url: None,
@ -94,7 +87,7 @@ impl SwfMovie {
) -> Result<Self, Error> {
let compressed_length = swf_data.len();
let swf_buf = swf::read::decompress_swf(swf_data)?;
let encoding = swf::SwfStr::encoding_for_version(swf_buf.header.version);
let encoding = swf::SwfStr::encoding_for_version(swf_buf.header.version());
Ok(Self {
header: swf_buf.header,
data: swf_buf.data,
@ -106,13 +99,13 @@ impl SwfMovie {
})
}
pub fn header(&self) -> &Header {
pub fn header(&self) -> &HeaderExt {
&self.header
}
/// Get the version of the SWF.
pub fn version(&self) -> u8 {
self.header.version
self.header.version()
}
pub fn data(&self) -> &[u8] {
@ -128,11 +121,11 @@ impl SwfMovie {
}
pub fn width(&self) -> u32 {
(self.header.stage_size.x_max - self.header.stage_size.x_min).to_pixels() as u32
(self.header.stage_size().x_max - self.header.stage_size().x_min).to_pixels() as u32
}
pub fn height(&self) -> u32 {
(self.header.stage_size.y_max - self.header.stage_size.y_min).to_pixels() as u32
(self.header.stage_size().y_max - self.header.stage_size().y_min).to_pixels() as u32
}
/// Get the URL this SWF was fetched from.
@ -296,7 +289,7 @@ impl SwfSlice {
/// Get the version of the SWF this data comes from.
pub fn version(&self) -> u8 {
self.movie.header().version
self.movie.header().version()
}
/// Construct a reader for this slice.

View File

@ -526,7 +526,7 @@ fn run_timedemo(opt: Opt) -> Result<(), Box<dyn std::error::Error>> {
.as_ref()
.ok_or("Input file necessary for timedemo")?;
let (movie, _) = load_movie_from_path(&path, &opt)?;
let movie_frames = Some(movie.header().num_frames);
let movie_frames = Some(movie.header().num_frames());
let viewport_width = 1920;
let viewport_height = 1080;

View File

@ -6,6 +6,6 @@ fn main() {
let reader = BufReader::new(file);
let swf_buf = swf::decompress_swf(reader).unwrap();
let swf = swf::parse_swf(&swf_buf).unwrap();
println!("The SWF has {} frame(s).", swf.header.num_frames);
println!("The SWF has {} frame(s).", swf.header.num_frames());
println!("The SWF has {} tag(s).", swf.tags.len());
}

View File

@ -1,31 +1,28 @@
use swf::*;
fn main() {
let swf = Swf {
header: Header {
compression: Compression::Zlib,
version: 6,
uncompressed_length: 29,
stage_size: Rectangle {
x_min: Twips::from_pixels(0.0),
x_max: Twips::from_pixels(400.0),
y_min: Twips::from_pixels(0.0),
y_max: Twips::from_pixels(400.0),
},
frame_rate: 60.0,
num_frames: 1,
let header = Header {
compression: Compression::Zlib,
version: 6,
stage_size: Rectangle {
x_min: Twips::from_pixels(0.0),
x_max: Twips::from_pixels(400.0),
y_min: Twips::from_pixels(0.0),
y_max: Twips::from_pixels(400.0),
},
tags: vec![
Tag::SetBackgroundColor(Color {
r: 255,
g: 0,
b: 0,
a: 255,
}),
Tag::ShowFrame,
],
frame_rate: 60.0,
num_frames: 1,
};
let tags = [
Tag::SetBackgroundColor(Color {
r: 255,
g: 0,
b: 0,
a: 255,
}),
Tag::ShowFrame,
];
let file = std::fs::File::create("tests/swfs/SimpleRedBackground.swf").unwrap();
let writer = std::io::BufWriter::new(file);
swf::write_swf(&swf, writer).unwrap();
swf::write_swf(&header, &tags, writer).unwrap();
}

View File

@ -25,10 +25,10 @@ use std::io::{self, Read};
/// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap();
/// let stream = swf::decompress_swf(&data[..]).unwrap();
/// let swf = swf::parse_swf(&stream).unwrap();
/// println!("Number of frames: {}", swf.header.num_frames);
/// println!("Number of frames: {}", swf.header.num_frames());
/// ```
pub fn parse_swf(swf_buf: &SwfBuf) -> Result<Swf<'_>> {
let mut reader = Reader::new(&swf_buf.data[..], swf_buf.header.version);
let mut reader = Reader::new(&swf_buf.data[..], swf_buf.header.version());
Ok(Swf {
header: swf_buf.header.clone(),
@ -41,18 +41,22 @@ pub fn parse_swf(swf_buf: &SwfBuf) -> Result<Swf<'_>> {
///
/// Returns an `Error` if this is not a valid SWF file.
///
/// This will also parse the first two tags of the SWF file searching
/// for the FileAttributes and SetBackgroundColor tags; this info is
/// returned as an extended header.
///
/// # Example
/// ```
/// # std::env::set_current_dir(env!("CARGO_MANIFEST_DIR"));
/// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap();
/// let swf_stream = swf::decompress_swf(&data[..]).unwrap();
/// println!("FPS: {}", swf_stream.header.frame_rate);
/// println!("FPS: {}", swf_stream.header.frame_rate());
/// ```
pub fn decompress_swf<'a, R: Read + 'a>(mut input: R) -> Result<SwfBuf> {
// Read SWF header.
let compression = read_compression_type(&mut input)?;
let version = input.read_u8()?;
let uncompressed_length = input.read_u32::<LittleEndian>()?;
let uncompressed_len = input.read_u32::<LittleEndian>()?;
// Now the SWF switches to a compressed stream.
let mut decompress_stream: Box<dyn Read> = match compression {
@ -75,12 +79,12 @@ pub fn decompress_swf<'a, R: Read + 'a>(mut input: R) -> Result<SwfBuf> {
}
// Uncompressed length includes the 4-byte header and 4-byte uncompressed length itself,
// subtract it here.
make_lzma_reader(input, uncompressed_length - 8)?
make_lzma_reader(input, uncompressed_len - 8)?
}
};
// Decompress the entire SWF.
let mut data = Vec::with_capacity(uncompressed_length as usize);
let mut data = Vec::with_capacity(uncompressed_len as usize);
if let Err(e) = decompress_stream.read_to_end(&mut data) {
log::error!("Error decompressing SWF: {}", e);
}
@ -91,7 +95,7 @@ pub fn decompress_swf<'a, R: Read + 'a>(mut input: R) -> Result<SwfBuf> {
// through the stream.
// We'll still try to parse what we get if the full decompression fails.
// (+ 8 for header size)
if data.len() as u64 + 8 != uncompressed_length as u64 {
if data.len() as u64 + 8 != uncompressed_len as u64 {
log::warn!("SWF length doesn't match header, may be corrupt");
}
@ -102,13 +106,45 @@ pub fn decompress_swf<'a, R: Read + 'a>(mut input: R) -> Result<SwfBuf> {
let header = Header {
compression,
version,
uncompressed_length,
stage_size,
frame_rate,
num_frames,
};
let data = reader.get_ref().to_vec();
Ok(SwfBuf { header, data })
// Parse the first two tags, searching for the FileAttributes and SetBackgroundColor tags.
// This metadata is useful, so we want to return it along with the header.
// In SWF8+, FileAttributes should be the first tag in the SWF.
// FileAttributes anywhere else in the SWF are ignored.
let mut tag = reader.read_tag();
let file_attributes = if let Ok(Tag::FileAttributes(attributes)) = tag {
tag = reader.read_tag();
attributes
} else {
FileAttributes::default()
};
// In most SWFs, SetBackgroundColor will be the second or third tag after FileAttributes + Metadata.
// It's possible for the SetBackgroundColor tag to be missing or appear later in wacky SWFs, so let's
// return `None` in this case.
let mut background_color = None;
for _ in 0..2 {
if let Ok(Tag::SetBackgroundColor(color)) = tag {
background_color = Some(color);
break;
};
tag = reader.read_tag();
}
Ok(SwfBuf {
header: HeaderExt {
header,
file_attributes,
background_color,
uncompressed_len,
},
data,
})
}
#[cfg(feature = "flate2")]
@ -304,7 +340,7 @@ impl<'a> Reader<'a> {
/// # std::env::set_current_dir(env!("CARGO_MANIFEST_DIR"));
/// let data = std::fs::read("tests/swfs/DefineSprite.swf").unwrap();
/// let mut swf_buf = swf::decompress_swf(&data[..]).unwrap();
/// let mut reader = swf::read::Reader::new(&swf_buf.data[..], swf_buf.header.version);
/// let mut reader = swf::read::Reader::new(&swf_buf.data[..], swf_buf.header.version());
/// while let Ok(tag) = reader.read_tag() {
/// println!("Tag: {:?}", tag);
/// }
@ -2698,7 +2734,7 @@ pub mod tests {
let mut tag_header_length;
loop {
let (swf_tag_code, length) = {
let mut tag_reader = Reader::new(&data[pos..], swf_buf.header.version);
let mut tag_reader = Reader::new(&data[pos..], swf_buf.header.version());
let ret = tag_reader.read_tag_code_and_length().unwrap();
tag_header_length =
tag_reader.get_ref().as_ptr() as usize - (pos + data.as_ptr() as usize);
@ -2746,16 +2782,16 @@ pub mod tests {
assert_eq!(
read_from_file("tests/swfs/uncompressed.swf")
.header
.compression,
.compression(),
Compression::None
);
assert_eq!(
read_from_file("tests/swfs/zlib.swf").header.compression,
read_from_file("tests/swfs/zlib.swf").header.compression(),
Compression::Zlib
);
if cfg!(feature = "lzma") {
assert_eq!(
read_from_file("tests/swfs/lzma.swf").header.compression,
read_from_file("tests/swfs/lzma.swf").header.compression(),
Compression::Lzma
);
}

View File

@ -18,7 +18,7 @@ pub fn echo_swf(filename: &str) {
let swf_buf = decompress_swf(&in_data[..]).unwrap();
let swf = parse_swf(&swf_buf).unwrap();
let out_file = File::create(filename).unwrap();
write_swf(&swf, out_file).unwrap();
write_swf(&swf.header.swf_header(), &swf.tags, out_file).unwrap();
}
pub type TestData<T> = (u8, T, Vec<u8>);

View File

@ -14,9 +14,9 @@ pub use matrix::Matrix;
/// A complete header and tags in the SWF file.
/// This is returned by the `swf::read_swf` convenience method.
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Swf<'a> {
pub header: Header,
pub header: HeaderExt,
pub tags: Vec<Tag<'a>>,
}
@ -24,7 +24,7 @@ pub struct Swf<'a> {
/// Owns the decompressed SWF data, which will be referenced when parsed by `parse_swf`.
pub struct SwfBuf {
/// The parsed SWF header.
pub header: Header,
pub header: HeaderExt,
/// The decompressed SWF tag stream.
pub data: Vec<u8>,
@ -39,17 +39,143 @@ pub struct SwfBuf {
pub struct Header {
pub compression: Compression,
pub version: u8,
pub uncompressed_length: u32,
pub stage_size: Rectangle,
pub frame_rate: f32,
pub num_frames: u16,
}
impl Header {
pub fn default_with_swf_version(version: u8) -> Self {
Self {
compression: Compression::None,
version,
stage_size: Default::default(),
frame_rate: 1.0,
num_frames: 0,
}
}
}
/// The extended metadata of an SWF file.
///
/// This includes the SWF header data as well as metdata from the FileAttributes and
/// SetBackgroundColor tags.
///
/// This metadata may not reflect the actual data inside a malformed SWF; for example,
/// the root timeline my actually contain fewer frames than `HeaderExt::num_frames` if it is
/// corrupted.
#[derive(Clone, Debug)]
pub struct HeaderExt {
pub(crate) header: Header,
pub(crate) file_attributes: FileAttributes,
pub(crate) background_color: Option<SetBackgroundColor>,
pub(crate) uncompressed_len: u32,
}
impl HeaderExt {
#[inline]
/// Returns the header for a dummy SWF file with the given SWF version.
pub fn default_with_swf_version(version: u8) -> Self {
Self {
header: Header::default_with_swf_version(version),
file_attributes: Default::default(),
background_color: None,
uncompressed_len: 0,
}
}
/// The background color of the SWF from the SetBackgroundColor tag.
///
/// `None` will be returned if the SetBackgroundColor tag was not found.
#[inline]
pub fn background_color(&self) -> Option<Color> {
self.background_color.clone()
}
/// The compression format used by the SWF.
#[inline]
pub fn compression(&self) -> Compression {
self.header.compression
}
/// The frame rate of the SWF, in frames per second.
#[inline]
pub fn frame_rate(&self) -> f32 {
self.header.frame_rate
}
/// Whether this SWF contains XMP metadata in a Metadata tag.
#[inline]
pub fn has_metdata(&self) -> bool {
self.file_attributes.contains(FileAttributes::HAS_METADATA)
}
/// Returns the basic SWF header.
#[inline]
pub fn swf_header(&self) -> &Header {
&self.header
}
/// Whether this SWF uses ActionScript 3.0 (AVM2).
#[inline]
pub fn is_action_script_3(&self) -> bool {
self.file_attributes
.contains(FileAttributes::IS_ACTION_SCRIPT_3)
}
/// The number of frames on the root timeline.
#[inline]
pub fn num_frames(&self) -> u16 {
self.header.num_frames
}
/// The stage dimensions of this SWF.
#[inline]
pub fn stage_size(&self) -> &Rectangle {
&self.header.stage_size
}
/// The SWF version.
#[inline]
pub fn version(&self) -> u8 {
self.header.version
}
/// The length of the SWF after decompression.
#[inline]
pub fn uncompressed_len(&self) -> u32 {
self.uncompressed_len
}
/// Whether this SWF requests hardware acceleration to blit to the screen.
#[inline]
pub fn use_direct_blit(&self) -> bool {
self.file_attributes
.contains(FileAttributes::USE_DIRECT_BLIT)
}
/// Whether this SWF requests hardware acceleration for compositing.
#[inline]
pub fn use_gpu(&self) -> bool {
self.file_attributes.contains(FileAttributes::USE_GPU)
}
/// Whether this SWF should be placed in the network sandbox when run locally.
///
/// SWFs in the network sandbox can only access network resources, not local resources.
/// SWFs in the local sandbox can only access local resources, not network resources.
#[inline]
pub fn use_network_sandbox(&self) -> bool {
self.file_attributes
.contains(FileAttributes::USE_NETWORK_SANDBOX)
}
}
/// The compression format used internally by the SWF file.
///
/// The vast majority of SWFs will use zlib compression.
/// [SWF19 p.27](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=27)
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Compression {
None,
Zlib,
@ -393,6 +519,13 @@ bitflags! {
}
}
impl Default for FileAttributes {
fn default() -> Self {
// The settings for SWF7 and earlier, which contain no FileAttributes tag.
Self::empty()
}
}
#[derive(Debug, PartialEq)]
pub struct FrameLabel<'a> {
pub label: &'a SwfStr,

View File

@ -21,43 +21,40 @@ use std::io::{self, Write};
/// ```
/// use swf::*;
///
/// let swf = Swf {
/// header: Header {
/// let header = Header {
/// compression: Compression::Zlib,
/// version: 6,
/// uncompressed_length: 1024,
/// stage_size: Rectangle { x_min: Twips::from_pixels(0.0), x_max: Twips::from_pixels(400.0), y_min: Twips::from_pixels(0.0), y_max: Twips::from_pixels(400.0) },
/// frame_rate: 60.0,
/// num_frames: 1,
/// },
/// tags: vec![
/// };
/// let tags = [
/// Tag::SetBackgroundColor(Color { r: 255, g: 0, b: 0, a: 255 }),
/// Tag::ShowFrame
/// ]
/// };
/// ];
/// let output = Vec::new();
/// swf::write_swf(&swf, output).unwrap();
/// swf::write_swf(&header, &tags, output).unwrap();
/// ```
pub fn write_swf<W: Write>(swf: &Swf, mut output: W) -> Result<()> {
let signature = match swf.header.compression {
pub fn write_swf<W: Write>(header: &Header, tags: &[Tag<'_>], mut output: W) -> Result<()> {
let signature = match header.compression {
Compression::None => b"FWS",
Compression::Zlib => b"CWS",
Compression::Lzma => b"ZWS",
};
output.write_all(&signature[..])?;
output.write_u8(swf.header.version)?;
output.write_u8(header.version)?;
// Write SWF body.
let mut swf_body = Vec::new();
{
let mut writer = Writer::new(&mut swf_body, swf.header.version);
let mut writer = Writer::new(&mut swf_body, header.version);
writer.write_rectangle(&swf.header.stage_size)?;
writer.write_fixed8(swf.header.frame_rate)?;
writer.write_u16(swf.header.num_frames)?;
writer.write_rectangle(&header.stage_size)?;
writer.write_fixed8(header.frame_rate)?;
writer.write_u16(header.num_frames)?;
// Write main timeline tag list.
writer.write_tag_list(&swf.tags)?;
writer.write_tag_list(&tags)?;
}
// Write SWF header.
@ -65,7 +62,7 @@ pub fn write_swf<W: Write>(swf: &Swf, mut output: W) -> Result<()> {
output.write_u32::<LittleEndian>(swf_body.len() as u32 + 8)?;
// Compress SWF body.
match swf.header.compression {
match header.compression {
Compression::None => {
output.write_all(&swf_body)?;
}
@ -2650,12 +2647,13 @@ mod tests {
use super::*;
use crate::test_data;
fn new_swf() -> Swf<'static> {
Swf {
header: Header {
compression: Compression::Zlib,
#[test]
fn write_swfs() {
fn write_dummy_swf(compression: Compression) -> Result<()> {
let mut buf = Vec::new();
let header = Header {
compression,
version: 13,
uncompressed_length: 1024,
stage_size: Rectangle {
x_min: Twips::from_pixels(0.0),
x_max: Twips::from_pixels(640.0),
@ -2664,18 +2662,8 @@ mod tests {
},
frame_rate: 60.0,
num_frames: 1,
},
tags: vec![],
}
}
#[test]
fn write_swfs() {
fn write_dummy_swf(compression: Compression) -> Result<()> {
let mut buf = Vec::new();
let mut swf = new_swf();
swf.header.compression = compression;
write_swf(&swf, &mut buf)?;
};
write_swf(&header, &[], &mut buf)?;
Ok(())
}
assert!(

View File

@ -880,7 +880,7 @@ fn run_swf(
let base_path = Path::new(swf_path).parent().unwrap();
let (mut executor, channel) = NullExecutor::new();
let movie = SwfMovie::from_path(swf_path, None)?;
let frame_time = 1000.0 / movie.header().frame_rate as f64;
let frame_time = 1000.0 / movie.header().frame_rate() as f64;
let trace_output = Rc::new(RefCell::new(Vec::new()));
let player = Player::new(

View File

@ -207,7 +207,7 @@ impl Ruffle {
let parameters_to_load = parse_movie_parameters(&parameters);
let ruffle = *self;
let on_metadata = move |swf_header: &ruffle_core::swf::Header| {
let on_metadata = move |swf_header: &ruffle_core::swf::HeaderExt| {
ruffle.on_metadata(swf_header);
};
@ -976,16 +976,16 @@ impl Ruffle {
});
}
fn on_metadata(&self, swf_header: &ruffle_core::swf::Header) {
fn on_metadata(&self, swf_header: &ruffle_core::swf::HeaderExt) {
let _ = self.with_instance(|instance| {
let width = swf_header.stage_size.x_max - swf_header.stage_size.x_min;
let height = swf_header.stage_size.y_max - swf_header.stage_size.y_min;
let width = swf_header.stage_size().x_max - swf_header.stage_size().x_min;
let height = swf_header.stage_size().y_max - swf_header.stage_size().y_min;
let metadata = MovieMetadata {
width: width.to_pixels(),
height: height.to_pixels(),
frame_rate: swf_header.frame_rate,
num_frames: swf_header.num_frames,
swf_version: swf_header.version,
frame_rate: swf_header.frame_rate(),
num_frames: swf_header.num_frames(),
swf_version: swf_header.version(),
};
if let Ok(value) = JsValue::from_serde(&metadata) {