swf_parser/complete/
tag.rs

1use crate::complete::base::{offset_take, skip};
2use crate::complete::button::{
3  parse_button2_cond_action_string, parse_button_record_string, parse_button_sound, ButtonVersion,
4};
5use crate::complete::display::{parse_blend_mode, parse_clip_actions_string, parse_filter_list};
6use crate::complete::image::{
7  get_gif_image_dimensions, get_jpeg_image_dimensions, get_png_image_dimensions, is_sniffed_jpeg, sniff_image_type,
8  SniffedImageType,
9};
10use crate::complete::morph_shape::{parse_morph_shape, MorphShapeVersion};
11use crate::complete::shape::{parse_glyph, parse_shape, ShapeVersion};
12use crate::complete::sound::{
13  audio_coding_format_from_code, is_uncompressed_audio_coding_format, parse_sound_info, sound_rate_from_code,
14};
15use crate::complete::text::{
16  grid_fitting_from_code, parse_csm_table_hint_bits, parse_font_alignment_zone, parse_font_layout, parse_offset_glyphs,
17  parse_text_alignment, parse_text_record_string, text_renderer_from_code, FontInfoVersion, FontVersion, TextVersion,
18};
19use crate::complete::video::{parse_videoc_codec, video_deblocking_from_code};
20use crate::streaming::basic_data_types::{
21  parse_block_c_string, parse_c_string, parse_color_transform, parse_color_transform_with_alpha, parse_language_code,
22  parse_leb128_u32, parse_matrix, parse_named_id, parse_rect, parse_s_rgb8, parse_straight_s_rgba8,
23};
24use crate::streaming::movie::parse_tag_block_string;
25use crate::streaming::tag::StreamingTagError;
26use nom::number::complete::{
27  le_f32 as parse_le_f32, le_i16 as parse_le_i16, le_u16 as parse_le_u16, le_u32 as parse_le_u32, le_u8 as parse_u8,
28};
29use nom::IResult as NomResult;
30use std::convert::TryFrom;
31use swf_types as ast;
32use swf_types::text::FontAlignmentZone;
33use swf_types::{AbcHeader, ButtonCondAction, Glyph};
34
35/// Parses the tag at the start of `input`.
36///
37/// This parser assumes that `input` is complete: it has all the data until the end of the movie.
38///
39/// This function returns the remaining input and the tag.
40/// `None` indicates the end of tags (empty input or `End` raw tag).
41/// This function always succeeds. Malformed tags produce a `Tag::Raw` containing the invalid bytes.
42pub fn parse_tag(input: &[u8], swf_version: u8) -> (&[u8], Option<ast::Tag>) {
43  if input.is_empty() {
44    return (input, None);
45  }
46
47  match crate::streaming::tag::parse_tag(input, swf_version) {
48    Ok(ok) => ok,
49    Err(StreamingTagError::IncompleteHeader) | Err(StreamingTagError::IncompleteTag(_)) => {
50      (&[][..], Some(ast::Tag::Raw(ast::tags::Raw { data: input.to_vec() })))
51    }
52  }
53}
54
55pub(crate) fn parse_tag_body(input: &[u8], code: u16, swf_version: u8) -> ast::Tag {
56  use nom::combinator::map;
57  let result = match code {
58    1 => Ok((input, ast::Tag::ShowFrame)),
59    2 => map(parse_define_shape, ast::Tag::DefineShape)(input),
60    4 => map(parse_place_object, ast::Tag::PlaceObject)(input),
61    5 => map(parse_remove_object, ast::Tag::RemoveObject)(input),
62    6 => map(|i| parse_define_bits(i, swf_version), ast::Tag::DefineBitmap)(input),
63    7 => map(parse_define_button, ast::Tag::DefineButton)(input),
64    8 => map(|i| parse_define_jpeg_tables(i, swf_version), ast::Tag::DefineJpegTables)(input),
65    9 => map(parse_set_background_color_tag, ast::Tag::SetBackgroundColor)(input),
66    10 => map(parse_define_font, ast::Tag::DefineGlyphFont)(input),
67    11 => map(parse_define_text, ast::Tag::DefineText)(input),
68    12 => map(parse_do_action, ast::Tag::DoAction)(input),
69    13 => map(parse_define_font_info, ast::Tag::DefineFontInfo)(input),
70    14 => map(parse_define_sound, ast::Tag::DefineSound)(input),
71    15 => map(parse_start_sound, ast::Tag::StartSound)(input),
72    17 => map(parse_define_button_sound, ast::Tag::DefineButtonSound)(input),
73    18 => map(parse_sound_stream_head, ast::Tag::SoundStreamHead)(input),
74    19 => map(parse_sound_stream_block, ast::Tag::SoundStreamBlock)(input),
75    20 => map(parse_define_bits_lossless, ast::Tag::DefineBitmap)(input),
76    21 => map(|i| parse_define_bits_jpeg2(i, swf_version), ast::Tag::DefineBitmap)(input),
77    22 => map(parse_define_shape2, ast::Tag::DefineShape)(input),
78    23 => map(
79      parse_define_button_color_transform,
80      ast::Tag::DefineButtonColorTransform,
81    )(input),
82    24 => map(parse_protect, ast::Tag::Protect)(input),
83    25 => Ok((input, ast::Tag::EnablePostscript)),
84    26 => map(|i| parse_place_object2(i, swf_version), ast::Tag::PlaceObject)(input),
85    28 => map(parse_remove_object2, ast::Tag::RemoveObject)(input),
86    32 => map(parse_define_shape3, ast::Tag::DefineShape)(input),
87    33 => map(parse_define_text2, ast::Tag::DefineText)(input),
88    34 => map(parse_define_button2, ast::Tag::DefineButton)(input),
89    35 => map(|i| parse_define_bits_jpeg3(i, swf_version), ast::Tag::DefineBitmap)(input),
90    36 => map(parse_define_bits_lossless2, ast::Tag::DefineBitmap)(input),
91    37 => map(parse_define_edit_text, ast::Tag::DefineDynamicText)(input),
92    39 => map(|i| parse_define_sprite(i, swf_version), ast::Tag::DefineSprite)(input),
93    43 => map(parse_frame_label, ast::Tag::FrameLabel)(input),
94    45 => map(parse_sound_stream_head2, ast::Tag::SoundStreamHead)(input),
95    46 => map(parse_define_morph_shape, ast::Tag::DefineMorphShape)(input),
96    48 => map(parse_define_font2, ast::Tag::DefineFont)(input),
97    56 => map(parse_export_assets, ast::Tag::ExportAssets)(input),
98    57 => map(parse_import_assets, ast::Tag::ImportAssets)(input),
99    58 => map(parse_enable_debugger, ast::Tag::EnableDebugger)(input),
100    59 => map(parse_do_init_action, ast::Tag::DoInitAction)(input),
101    60 => map(parse_define_video_stream, ast::Tag::DefineVideoStream)(input),
102    61 => map(parse_video_frame, ast::Tag::VideoFrame)(input),
103    62 => map(parse_define_font_info2, ast::Tag::DefineFontInfo)(input),
104    64 => map(parse_enable_debugger2, ast::Tag::EnableDebugger)(input),
105    65 => map(parse_script_limits, ast::Tag::ScriptLimits)(input),
106    66 => map(parse_set_tab_index, ast::Tag::SetTabIndex)(input),
107    69 => map(parse_file_attributes_tag, ast::Tag::FileAttributes)(input),
108    70 => map(|i| parse_place_object3(i, swf_version), ast::Tag::PlaceObject)(input),
109    71 => map(parse_import_assets2, ast::Tag::ImportAssets)(input),
110    72 => map(|i| parse_do_abc(i, false), ast::Tag::DoAbc)(input),
111    73 => map(parse_define_font_align_zones, ast::Tag::DefineFontAlignZones)(input),
112    74 => map(parse_csm_text_settings, ast::Tag::CsmTextSettings)(input),
113    75 => map(parse_define_font3, ast::Tag::DefineFont)(input),
114    76 => map(parse_symbol_class, ast::Tag::SymbolClass)(input),
115    77 => map(parse_metadata, ast::Tag::Metadata)(input),
116    78 => map(parse_define_scaling_grid, ast::Tag::DefineScalingGrid)(input),
117    82 => map(|i| parse_do_abc(i, true), ast::Tag::DoAbc)(input),
118    83 => map(parse_define_shape4, ast::Tag::DefineShape)(input),
119    84 => map(parse_define_morph_shape2, ast::Tag::DefineMorphShape)(input),
120    86 => map(
121      parse_define_scene_and_frame_label_data_tag,
122      ast::Tag::DefineSceneAndFrameLabelData,
123    )(input),
124    87 => map(parse_define_binary_data, ast::Tag::DefineBinaryData)(input),
125    88 => map(parse_define_font_name, ast::Tag::DefineFontName)(input),
126    89 => map(parse_start_sound2, ast::Tag::StartSound2)(input),
127    90 => map(parse_define_bits_jpeg4, ast::Tag::DefineBitmap)(input),
128    91 => map(parse_define_font4, ast::Tag::DefineCffFont)(input),
129    93 => map(parse_enable_telemetry, ast::Tag::Telemetry)(input),
130    _ => Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Switch))),
131  };
132  match result {
133    Ok((_, tag)) => tag,
134    Err(_) => ast::Tag::RawBody(ast::tags::RawBody {
135      code,
136      data: input.to_vec(),
137    }),
138  }
139}
140
141pub fn parse_csm_text_settings(input: &[u8]) -> NomResult<&[u8], ast::tags::CsmTextSettings> {
142  let (input, text_id) = parse_le_u16(input)?;
143  let (input, flags) = parse_u8(input)?;
144  // Skip bits [0, 2]
145  let fitting = grid_fitting_from_code((flags >> 3) & 0b111)
146    .map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Switch)))?;
147  let renderer = text_renderer_from_code((flags >> 6) & 0b11)
148    .map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Switch)))?;
149  let (input, thickness) = parse_le_f32(input)?;
150  let (input, sharpness) = parse_le_f32(input)?;
151  // TODO: Skip 1 byte / assert 1 byte is available
152  Ok((
153    input,
154    ast::tags::CsmTextSettings {
155      text_id,
156      renderer,
157      fitting,
158      thickness,
159      sharpness,
160    },
161  ))
162}
163
164pub fn parse_define_binary_data(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineBinaryData> {
165  let (input, id) = parse_le_u16(input)?;
166  let (input, _reserved) = parse_le_u32(input)?; // TODO: assert reserved == 0
167  let data = input.to_vec();
168  let input = &[][..];
169  Ok((input, ast::tags::DefineBinaryData { id, data }))
170}
171
172pub fn parse_define_bits(input: &[u8], swf_version: u8) -> NomResult<&[u8], ast::tags::DefineBitmap> {
173  let (input, id) = parse_le_u16(input)?;
174  let data: Vec<u8> = input.to_vec();
175  let input: &[u8] = &[][..];
176
177  if is_sniffed_jpeg(&data, swf_version < 8) {
178    let image_dimensions =
179      get_jpeg_image_dimensions(&data).map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)))?;
180    // TODO: avoid conversions
181    Ok((
182      input,
183      ast::tags::DefineBitmap {
184        id,
185        width: image_dimensions.width as u16,
186        height: image_dimensions.height as u16,
187        media_type: ast::ImageType::SwfPartialJpeg,
188        data,
189      },
190    ))
191  } else {
192    // UnknownBitmapType
193    // TODO: Better error
194    Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)))
195  }
196}
197
198pub fn parse_define_button(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineButton> {
199  let (input, id) = parse_le_u16(input)?;
200
201  let (input, records) = parse_button_record_string(input, ButtonVersion::Button1)?;
202  let actions = input.to_vec();
203  let cond_action = ButtonCondAction {
204    conditions: None,
205    actions,
206  };
207
208  Ok((
209    input,
210    ast::tags::DefineButton {
211      id,
212      track_as_menu: false,
213      records,
214      actions: vec![cond_action],
215    },
216  ))
217}
218
219pub fn parse_define_button2(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineButton> {
220  use nom::combinator::map;
221
222  let (input, id) = parse_le_u16(input)?;
223  let (input, flags) = parse_u8(input)?;
224  #[allow(clippy::identity_op)]
225  let track_as_menu = (flags & (1 << 0)) != 0;
226  // Skip bits [1, 7]
227  // TODO: Assert action offset matches
228  let (input, action_offset) = map(parse_le_u16, |x| x as usize)(input)?;
229  let (input, records) = parse_button_record_string(input, ButtonVersion::Button2)?;
230  let (input, actions) = if action_offset != 0 {
231    parse_button2_cond_action_string(input)?
232  } else {
233    (input, Vec::new())
234  };
235
236  Ok((
237    input,
238    ast::tags::DefineButton {
239      id,
240      track_as_menu,
241      records,
242      actions,
243    },
244  ))
245}
246
247pub fn parse_define_button_color_transform(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineButtonColorTransform> {
248  let (input, button_id) = parse_le_u16(input)?;
249  let (input, transform) = parse_color_transform(input)?;
250
251  Ok((input, ast::tags::DefineButtonColorTransform { button_id, transform }))
252}
253
254pub fn parse_define_button_sound(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineButtonSound> {
255  let (input, button_id) = parse_le_u16(input)?;
256  let (input, over_up_to_idle) = parse_button_sound(input)?;
257  let (input, idle_to_over_up) = parse_button_sound(input)?;
258  let (input, over_up_to_over_down) = parse_button_sound(input)?;
259  let (input, over_down_to_over_up) = parse_button_sound(input)?;
260
261  Ok((
262    input,
263    ast::tags::DefineButtonSound {
264      button_id,
265      over_up_to_idle,
266      idle_to_over_up,
267      over_up_to_over_down,
268      over_down_to_over_up,
269    },
270  ))
271}
272
273pub fn parse_define_bits_jpeg2(input: &[u8], swf_version: u8) -> NomResult<&[u8], ast::tags::DefineBitmap> {
274  let (input, id) = parse_le_u16(input)?;
275  let data: Vec<u8> = input.to_vec();
276  let input: &[u8] = &[][..];
277
278  let (media_type, dimensions) = match sniff_image_type(&data, swf_version < 8) {
279    Ok(SniffedImageType::Jpeg) => (ast::ImageType::Jpeg, get_jpeg_image_dimensions(&data)),
280    Ok(SniffedImageType::Png) => (ast::ImageType::Png, get_png_image_dimensions(&data)),
281    Ok(SniffedImageType::Gif) => (ast::ImageType::Gif, get_gif_image_dimensions(&data)),
282    Err(()) => {
283      // UnknownBitmapType
284      return Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)));
285    }
286  };
287
288  let dimensions = dimensions.map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)))?;
289
290  Ok((
291    input,
292    ast::tags::DefineBitmap {
293      id,
294      width: dimensions.width as u16,
295      height: dimensions.height as u16,
296      media_type,
297      data,
298    },
299  ))
300}
301
302pub fn parse_define_bits_jpeg3(input: &[u8], swf_version: u8) -> NomResult<&[u8], ast::tags::DefineBitmap> {
303  use nom::bytes::complete::take;
304  use nom::combinator::map;
305
306  let (ajpeg_data, id) = parse_le_u16(input)?;
307  let (input, data_len) = map(parse_le_u32, |x| x as usize)(ajpeg_data)?;
308  let (_, data) = take(data_len)(input)?;
309
310  let (media_type, dimensions, img_data) = match sniff_image_type(data, swf_version < 8) {
311    Ok(SniffedImageType::Jpeg) => {
312      let dimensions = get_jpeg_image_dimensions(data);
313      if input.len() > data_len {
314        (ast::ImageType::SwfJpeg3, dimensions, ajpeg_data)
315      } else {
316        (ast::ImageType::Jpeg, dimensions, data)
317      }
318    }
319    Ok(SniffedImageType::Png) => (ast::ImageType::Png, get_png_image_dimensions(data), data),
320    Ok(SniffedImageType::Gif) => (ast::ImageType::Gif, get_gif_image_dimensions(data), data),
321    Err(()) => {
322      // UnknownBitmapType
323      return Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)));
324    }
325  };
326
327  let dimensions = dimensions.map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)))?;
328
329  Ok((
330    &[],
331    ast::tags::DefineBitmap {
332      id,
333      width: dimensions.width as u16,
334      height: dimensions.height as u16,
335      media_type,
336      data: img_data.to_vec(),
337    },
338  ))
339}
340
341pub fn parse_define_bits_jpeg4(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineBitmap> {
342  use nom::bytes::complete::take;
343  use nom::combinator::map;
344
345  let (djpeg_data, id) = parse_le_u16(input)?;
346  let (input, data_len) = map(parse_le_u32, |dl| usize::try_from(dl).unwrap())(djpeg_data)?;
347  let (input, _) = skip(2usize)(input)?; // Skip deblock
348  let (_, data) = take(data_len)(input)?;
349
350  let (media_type, dimensions, img_data) = match sniff_image_type(data, false) {
351    Ok(SniffedImageType::Jpeg) => (ast::ImageType::SwfJpeg4, get_jpeg_image_dimensions(data), djpeg_data),
352    Ok(SniffedImageType::Png) => (ast::ImageType::Png, get_png_image_dimensions(data), data),
353    Ok(SniffedImageType::Gif) => (ast::ImageType::Gif, get_gif_image_dimensions(data), data),
354    Err(()) => {
355      // UnknownBitmapType
356      return Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)));
357    }
358  };
359
360  let dimensions = dimensions.map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)))?;
361
362  Ok((
363    &[],
364    ast::tags::DefineBitmap {
365      id,
366      width: dimensions.width as u16,
367      height: dimensions.height as u16,
368      media_type,
369      data: img_data.to_vec(),
370    },
371  ))
372}
373
374pub fn parse_define_bits_lossless(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineBitmap> {
375  parse_define_bits_lossless_any(input, ast::ImageType::SwfLossless1)
376}
377
378pub fn parse_define_bits_lossless2(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineBitmap> {
379  parse_define_bits_lossless_any(input, ast::ImageType::SwfLossless2)
380}
381
382fn parse_define_bits_lossless_any(
383  input: &[u8],
384  media_type: ast::ImageType,
385) -> NomResult<&[u8], ast::tags::DefineBitmap> {
386  let (input, id) = parse_le_u16(input)?;
387  let data: Vec<u8> = input.to_vec();
388  let (input, _) = skip(1usize)(input)?; // BitmapFormat
389  let (input, width) = parse_le_u16(input)?;
390  let (_, height) = parse_le_u16(input)?;
391  let input: &[u8] = &[];
392
393  Ok((
394    input,
395    ast::tags::DefineBitmap {
396      id,
397      width,
398      height,
399      media_type,
400      data,
401    },
402  ))
403}
404
405pub fn parse_define_edit_text(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineDynamicText> {
406  use nom::combinator::cond;
407  use nom::combinator::map;
408
409  let (input, id) = parse_le_u16(input)?;
410  let (input, bounds) = parse_rect(input)?;
411  let (input, flags) = parse_le_u16(input)?;
412  #[allow(clippy::identity_op)]
413  let has_font = (flags & (1 << 0)) != 0;
414  let has_max_length = (flags & (1 << 1)) != 0;
415  let has_color = (flags & (1 << 2)) != 0;
416  let readonly = (flags & (1 << 3)) != 0;
417  let password = (flags & (1 << 4)) != 0;
418  let multiline = (flags & (1 << 5)) != 0;
419  let word_wrap = (flags & (1 << 6)) != 0;
420  let has_text = (flags & (1 << 7)) != 0;
421  let use_glyph_font = (flags & (1 << 8)) != 0;
422  let html = (flags & (1 << 9)) != 0;
423  let was_static = (flags & (1 << 10)) != 0;
424  let border = (flags & (1 << 11)) != 0;
425  let no_select = (flags & (1 << 12)) != 0;
426  let has_layout = (flags & (1 << 13)) != 0;
427  let auto_size = (flags & (1 << 14)) != 0;
428  let has_font_class = (flags & (1 << 15)) != 0;
429
430  let (input, font_id) = cond(has_font, parse_le_u16)(input)?;
431  let (input, font_class) = cond(has_font_class, parse_c_string)(input)?;
432  let (input, font_size) = cond(has_font, parse_le_u16)(input)?;
433  let (input, color) = cond(has_color, parse_straight_s_rgba8)(input)?;
434  let (input, max_length) = cond(has_max_length, map(parse_le_u16, usize::from))(input)?;
435  let (input, align, margin_left, margin_right, indent, leading) = if has_layout {
436    let (input, align) = parse_text_alignment(input)?;
437    let (input, margin_left) = parse_le_u16(input)?;
438    let (input, margin_right) = parse_le_u16(input)?;
439    let (input, indent) = parse_le_u16(input)?;
440    let (input, leading) = parse_le_i16(input)?;
441    (input, align, margin_left, margin_right, indent, leading)
442  } else {
443    (input, ast::text::TextAlignment::Left, 0, 0, 0, 0)
444  };
445  let (input, variable_name) = parse_c_string(input)?;
446  let variable_name = if variable_name.is_empty() {
447    None
448  } else {
449    Some(variable_name)
450  };
451  let (input, text) = cond(has_text, parse_c_string)(input)?;
452
453  Ok((
454    input,
455    ast::tags::DefineDynamicText {
456      id,
457      bounds,
458      word_wrap,
459      multiline,
460      password,
461      readonly,
462      auto_size,
463      no_select,
464      border,
465      was_static,
466      html,
467      use_glyph_font,
468      font_id,
469      font_class,
470      font_size,
471      color,
472      max_length,
473      align,
474      margin_left,
475      margin_right,
476      indent,
477      leading,
478      variable_name,
479      text,
480    },
481  ))
482}
483
484pub fn parse_define_font(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineGlyphFont> {
485  let (input, id) = parse_le_u16(input)?;
486  let available = input.len();
487  let mut glyphs: Vec<Glyph> = Vec::new();
488
489  if available > 0 {
490    let saved_input: &[u8] = input;
491
492    let (mut input, first_offset) = parse_le_u16(input)?;
493    let first_offset: usize = first_offset.into();
494    // TODO: assert `first_offset` is even.
495    let glyph_count: usize = first_offset / 2;
496    let mut offsets: Vec<usize> = Vec::with_capacity(glyph_count);
497    offsets.push(first_offset);
498    for _ in 1..glyph_count {
499      let (next_input, offset) = parse_le_u16(input)?;
500      input = next_input;
501      offsets.push(offset.into());
502    }
503    let saved_input_len: usize = saved_input.len();
504    for i in 0..glyph_count {
505      let glyph_input = {
506        let start_offset = offsets[i];
507        let end_offset = offsets.get(i + 1).cloned().unwrap_or(saved_input_len);
508        let glyph_input_size: usize = match end_offset.checked_sub(start_offset) {
509          Some(x) => x,
510          None => return Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify))),
511        };
512        let (_, glyph_input) = offset_take(start_offset, glyph_input_size)(saved_input)?;
513        glyph_input
514      };
515      match parse_glyph(glyph_input) {
516        Ok((_, glyph)) => glyphs.push(glyph),
517        Err(e) => return Err(e),
518      };
519    }
520  }
521
522  Ok((&[], ast::tags::DefineGlyphFont { id, glyphs }))
523}
524
525pub fn parse_define_font2(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineFont> {
526  parse_define_font_any(input, FontVersion::Font2)
527}
528
529pub fn parse_define_font3(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineFont> {
530  parse_define_font_any(input, FontVersion::Font3)
531}
532
533// https://github.com/mozilla/shumway/blob/16451d8836fa85f4b16eeda8b4bda2fa9e2b22b0/src/swf/parser/module.ts#L632
534fn parse_define_font_any(input: &[u8], version: FontVersion) -> NomResult<&[u8], ast::tags::DefineFont> {
535  use nom::bytes::complete::take;
536  use nom::combinator::{cond, map};
537  use nom::multi::count;
538
539  let (input, id) = parse_le_u16(input)?;
540
541  let (input, flags) = parse_u8(input)?;
542  #[allow(clippy::identity_op)]
543  let is_bold = (flags & (1 << 0)) != 0;
544  let is_italic = (flags & (1 << 1)) != 0;
545  let use_wide_codes = (flags & (1 << 2)) != 0;
546  let use_wide_offsets = (flags & (1 << 3)) != 0;
547  let is_ansi = (flags & (1 << 4)) != 0;
548  let is_small = (flags & (1 << 5)) != 0;
549  let is_shift_jis = (flags & (1 << 6)) != 0;
550  let has_layout = (flags & (1 << 7)) != 0;
551
552  let em_square_size = if version >= FontVersion::Font3 {
553    ast::text::EmSquareSize::EmSquareSize20480
554  } else {
555    ast::text::EmSquareSize::EmSquareSize1024
556  };
557
558  let (input, language) = parse_language_code(input)?;
559  let (input, font_name) = {
560    let (input, font_name_len) = map(parse_u8, usize::from)(input)?;
561    let (input, font_name_bytes) = take(font_name_len)(input)?;
562    let (_, font_name) = parse_block_c_string(font_name_bytes)?;
563    (input, font_name)
564  };
565  let (input, glyph_count) = map(parse_le_u16, |x| x as usize)(input)?;
566
567  // According to Shumway:
568  // > The SWF format docs doesn't say that, but the DefineFont{2,3} tag ends here for device fonts.
569  // See the sample `open-flash-db/tags/define-font-df3-system-font-verdana`.
570  if glyph_count == 0 {
571    Ok((
572      input,
573      ast::tags::DefineFont {
574        id,
575        font_name,
576        is_bold,
577        is_italic,
578        is_ansi,
579        is_small,
580        is_shift_jis,
581        em_square_size,
582        language,
583        glyphs: None,
584        code_units: None,
585        layout: None,
586      },
587    ))
588  } else {
589    let (input, glyphs) = parse_offset_glyphs(input, glyph_count, use_wide_offsets)?;
590    let (input, code_units) = if use_wide_codes {
591      count(parse_le_u16, glyph_count)(input)?
592    } else {
593      count(map(parse_u8, u16::from), glyph_count)(input)?
594    };
595    let (input, layout) = cond(has_layout, |i| parse_font_layout(i, glyph_count))(input)?;
596    Ok((
597      input,
598      ast::tags::DefineFont {
599        id,
600        font_name,
601        is_bold,
602        is_italic,
603        is_ansi,
604        is_small,
605        is_shift_jis,
606        em_square_size,
607        language,
608        glyphs: Option::Some(glyphs),
609        code_units: Option::Some(code_units),
610        layout,
611      },
612    ))
613  }
614}
615
616pub fn parse_define_font4(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineCffFont> {
617  use nom::combinator::cond;
618
619  let (input, id) = parse_le_u16(input)?;
620  let (input, font_name) = parse_c_string(input)?;
621
622  let (input, flags) = parse_u8(input)?;
623  #[allow(clippy::identity_op)]
624  let is_bold = (flags & (1 << 0)) != 0;
625  let is_italic = (flags & (1 << 1)) != 0;
626  let has_data = (flags & (1 << 2)) != 0;
627  // Bits [3, 7] are reserved
628
629  let (input, data) = cond(has_data, parse_bytes)(input)?;
630
631  Ok((
632    input,
633    ast::tags::DefineCffFont {
634      id,
635      font_name,
636      is_bold,
637      is_italic,
638      data,
639    },
640  ))
641}
642
643pub fn parse_define_font_align_zones(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineFontAlignZones> {
644  use nom::bits::bits;
645
646  let (input, font_id) = parse_le_u16(input)?;
647  let (mut input, csm_table_hint) = bits(parse_csm_table_hint_bits)(input)?;
648  let mut zones: Vec<FontAlignmentZone> = Vec::new();
649  while !input.is_empty() {
650    let (next_input, zone) = parse_font_alignment_zone(input)?;
651    input = next_input;
652    zones.push(zone);
653  }
654
655  Ok((
656    input,
657    ast::tags::DefineFontAlignZones {
658      font_id,
659      csm_table_hint,
660      zones,
661    },
662  ))
663}
664
665pub fn parse_define_font_info(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineFontInfo> {
666  parse_define_font_info_any(input, FontInfoVersion::FontInfo1)
667}
668
669pub fn parse_define_font_info2(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineFontInfo> {
670  parse_define_font_info_any(input, FontInfoVersion::FontInfo2)
671}
672
673fn parse_define_font_info_any(input: &[u8], version: FontInfoVersion) -> NomResult<&[u8], ast::tags::DefineFontInfo> {
674  use nom::bytes::complete::take;
675  use nom::combinator::map;
676
677  fn parse_code_units(mut input: &[u8], use_wide_codes: bool) -> NomResult<&[u8], Vec<u16>> {
678    if use_wide_codes {
679      // TODO: Handle odd values
680      let code_unit_count = input.len() / 2;
681      let mut code_units: Vec<u16> = Vec::with_capacity(code_unit_count);
682
683      for _ in 0..code_unit_count {
684        let (next_input, code_unit) = parse_le_u16(input)?;
685        input = next_input;
686        code_units.push(code_unit);
687      }
688
689      Ok((input, code_units))
690    } else {
691      let code_units: Vec<u16> = input.iter().map(|x| u16::from(*x)).collect();
692      Ok((&[][..], code_units))
693    }
694  }
695
696  let (input, font_id) = parse_le_u16(input)?;
697  let (input, font_name) = {
698    let (input, font_name_len) = map(parse_u8, usize::from)(input)?;
699    let (input, font_name_bytes) = take(font_name_len)(input)?;
700    let (_, font_name) = parse_block_c_string(font_name_bytes)?;
701    (input, font_name)
702  };
703  let (input, flags) = parse_u8(input)?;
704  #[allow(clippy::identity_op)]
705  let use_wide_codes = (flags & (1 << 0)) != 0;
706  let is_bold = (flags & (1 << 1)) != 0;
707  let is_italic = (flags & (1 << 2)) != 0;
708  let is_ansi = (flags & (1 << 3)) != 0;
709  let is_shift_jis = (flags & (1 << 4)) != 0;
710  let is_small = (flags & (1 << 5)) != 0;
711  let (input, language) = if version >= FontInfoVersion::FontInfo2 {
712    parse_language_code(input)?
713  } else {
714    (input, ast::LanguageCode::Auto)
715  };
716  let (input, code_units) = parse_code_units(input, use_wide_codes)?;
717
718  Ok((
719    input,
720    ast::tags::DefineFontInfo {
721      font_id,
722      font_name,
723      is_bold,
724      is_italic,
725      is_ansi,
726      is_shift_jis,
727      is_small,
728      language,
729      code_units,
730    },
731  ))
732}
733
734pub fn parse_define_font_name(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineFontName> {
735  let (input, font_id) = parse_le_u16(input)?;
736  let (input, name) = parse_c_string(input)?;
737  let (input, copyright) = parse_c_string(input)?;
738  Ok((
739    input,
740    ast::tags::DefineFontName {
741      font_id,
742      name,
743      copyright,
744    },
745  ))
746}
747
748pub fn parse_define_jpeg_tables(input: &[u8], _swf_version: u8) -> NomResult<&[u8], ast::tags::DefineJpegTables> {
749  let data: Vec<u8> = input.to_vec();
750  let input: &[u8] = &[][..];
751
752  //  if !(test_image_start(&data, &JPEG_START) || (swf_version < 8 && test_image_start(&data, &ERRONEOUS_JPEG_START))) {
753  //    panic!("InvalidJpegTablesSignature");
754  //  }
755
756  Ok((input, ast::tags::DefineJpegTables { data }))
757}
758
759pub fn parse_define_morph_shape(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineMorphShape> {
760  parse_define_morph_shape_any(input, MorphShapeVersion::MorphShape1)
761}
762
763pub fn parse_define_morph_shape2(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineMorphShape> {
764  parse_define_morph_shape_any(input, MorphShapeVersion::MorphShape2)
765}
766
767fn parse_define_morph_shape_any(
768  input: &[u8],
769  version: MorphShapeVersion,
770) -> NomResult<&[u8], ast::tags::DefineMorphShape> {
771  use nom::combinator::cond;
772
773  let (input, id) = parse_le_u16(input)?;
774  let (input, bounds) = parse_rect(input)?;
775  let (input, morph_bounds) = parse_rect(input)?;
776  let (input, edge_bounds) = cond(version >= MorphShapeVersion::MorphShape2, parse_rect)(input)?;
777  let (input, morph_edge_bounds) = cond(version >= MorphShapeVersion::MorphShape2, parse_rect)(input)?;
778
779  let (input, flags) = cond(version >= MorphShapeVersion::MorphShape2, parse_u8)(input)?;
780  let flags = flags.unwrap_or(0);
781  #[allow(clippy::identity_op)]
782  let has_scaling_strokes = (flags & (1 << 0)) != 0;
783  let has_non_scaling_strokes = (flags & (1 << 1)) != 0;
784  // (Skip bits [2, 7])
785
786  let (input, shape) = parse_morph_shape(input, version)?;
787
788  Ok((
789    input,
790    ast::tags::DefineMorphShape {
791      id,
792      bounds,
793      morph_bounds,
794      edge_bounds,
795      morph_edge_bounds,
796      has_scaling_strokes,
797      has_non_scaling_strokes,
798      shape,
799    },
800  ))
801}
802
803pub fn parse_define_scaling_grid(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineScalingGrid> {
804  let (input, character_id) = parse_le_u16(input)?;
805  let (input, splitter) = parse_rect(input)?;
806  Ok((input, ast::tags::DefineScalingGrid { character_id, splitter }))
807}
808
809pub fn parse_define_scene_and_frame_label_data_tag(
810  input: &[u8],
811) -> NomResult<&[u8], ast::tags::DefineSceneAndFrameLabelData> {
812  use nom::combinator::map;
813  use nom::multi::count;
814  let (input, scene_count) = map(parse_leb128_u32, |x| usize::try_from(x).unwrap())(input)?;
815  let (input, scenes) = count(parse_scene, scene_count)(input)?;
816  let (input, label_count) = map(parse_leb128_u32, |x| usize::try_from(x).unwrap())(input)?;
817  let (input, labels) = count(parse_label, label_count)(input)?;
818
819  fn parse_scene(input: &[u8]) -> NomResult<&[u8], ast::control::Scene> {
820    let (input, offset) = parse_leb128_u32(input)?;
821    let (input, name) = parse_c_string(input)?;
822    Ok((input, ast::control::Scene { offset, name }))
823  }
824
825  fn parse_label(input: &[u8]) -> NomResult<&[u8], ast::control::Label> {
826    let (input, frame) = parse_leb128_u32(input)?;
827    let (input, name) = parse_c_string(input)?;
828    Ok((input, ast::control::Label { frame, name }))
829  }
830
831  Ok((input, ast::tags::DefineSceneAndFrameLabelData { scenes, labels }))
832}
833
834pub fn parse_define_shape(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineShape> {
835  parse_define_shape_any(input, ShapeVersion::Shape1)
836}
837
838pub fn parse_define_shape2(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineShape> {
839  parse_define_shape_any(input, ShapeVersion::Shape2)
840}
841
842pub fn parse_define_shape3(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineShape> {
843  parse_define_shape_any(input, ShapeVersion::Shape3)
844}
845
846pub fn parse_define_shape4(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineShape> {
847  parse_define_shape_any(input, ShapeVersion::Shape4)
848}
849
850fn parse_define_shape_any(input: &[u8], version: ShapeVersion) -> NomResult<&[u8], ast::tags::DefineShape> {
851  use nom::combinator::cond;
852
853  let (input, id) = parse_le_u16(input)?;
854  let (input, bounds) = parse_rect(input)?;
855  let (input, edge_bounds) = cond(version >= ShapeVersion::Shape4, parse_rect)(input)?;
856
857  let (input, flags) = cond(version >= ShapeVersion::Shape4, parse_u8)(input)?;
858  let flags = flags.unwrap_or(0);
859  #[allow(clippy::identity_op)]
860  let has_scaling_strokes = (flags & (1 << 0)) != 0;
861  let has_non_scaling_strokes = (flags & (1 << 1)) != 0;
862  let has_fill_winding = (flags & (1 << 2)) != 0;
863  // (Skip bits [3, 7])
864
865  let (input, shape) = parse_shape(input, version)?;
866
867  Ok((
868    input,
869    ast::tags::DefineShape {
870      id,
871      bounds,
872      edge_bounds,
873      has_fill_winding,
874      has_non_scaling_strokes,
875      has_scaling_strokes,
876      shape,
877    },
878  ))
879}
880
881fn parse_bytes(input: &[u8]) -> NomResult<&[u8], Vec<u8>> {
882  Ok((&[][..], input.to_vec()))
883}
884
885fn parse_define_sound(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineSound> {
886  let (input, id) = parse_le_u16(input)?;
887  let (input, flags) = parse_u8(input)?;
888  #[allow(clippy::identity_op)]
889  let sound_type = if (flags & (1 << 0)) != 0 {
890    ast::SoundType::Stereo
891  } else {
892    ast::SoundType::Mono
893  };
894  let sound_size = if (flags & (1 << 1)) != 0 {
895    ast::SoundSize::SoundSize16
896  } else {
897    ast::SoundSize::SoundSize8
898  };
899  let sound_rate =
900    sound_rate_from_code((flags >> 2) & 0b11).map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Switch)))?;
901  let format = audio_coding_format_from_code((flags >> 4) & 0b1111)
902    .map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Switch)))?;
903  let (input, sample_count) = parse_le_u32(input)?;
904  let (input, data) = parse_bytes(input)?;
905
906  Ok((
907    input,
908    ast::tags::DefineSound {
909      id,
910      sound_type,
911      sound_size: if is_uncompressed_audio_coding_format(format) {
912        sound_size
913      } else {
914        ast::SoundSize::SoundSize16
915      },
916      sound_rate,
917      format,
918      sample_count,
919      data,
920    },
921  ))
922}
923
924pub fn parse_define_sprite(input: &[u8], swf_version: u8) -> NomResult<&[u8], ast::tags::DefineSprite> {
925  use nom::combinator::map;
926
927  let (input, id) = parse_le_u16(input)?;
928  let (input, frame_count) = map(parse_le_u16, usize::from)(input)?;
929  let (input, tags) = parse_tag_block_string(input, swf_version)?;
930  Ok((input, ast::tags::DefineSprite { id, frame_count, tags }))
931}
932
933pub fn parse_define_text(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineText> {
934  parse_define_text_any(input, TextVersion::Text1)
935}
936
937pub fn parse_define_text2(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineText> {
938  parse_define_text_any(input, TextVersion::Text2)
939}
940
941pub fn parse_define_text_any(input: &[u8], version: TextVersion) -> NomResult<&[u8], ast::tags::DefineText> {
942  use nom::combinator::map;
943
944  let (input, id) = parse_le_u16(input)?;
945  let (input, bounds) = parse_rect(input)?;
946  let (input, matrix) = parse_matrix(input)?;
947  let (input, index_bits) = map(parse_u8, usize::from)(input)?;
948  let (input, advance_bits) = map(parse_u8, usize::from)(input)?;
949  let has_alpha = version >= TextVersion::Text2;
950  if index_bits > 32 || advance_bits > 32 {
951    return Err(nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Verify)));
952  }
953  let (input, records) = parse_text_record_string(input, has_alpha, index_bits, advance_bits)?;
954
955  Ok((
956    input,
957    ast::tags::DefineText {
958      id,
959      bounds,
960      matrix,
961      records,
962    },
963  ))
964}
965
966pub fn parse_define_video_stream(input: &[u8]) -> NomResult<&[u8], ast::tags::DefineVideoStream> {
967  use nom::combinator::map;
968
969  let (input, id) = parse_le_u16(input)?;
970  let (input, frame_count) = map(parse_le_u16, usize::from)(input)?;
971  let (input, width) = parse_le_u16(input)?;
972  let (input, height) = parse_le_u16(input)?;
973  let (input, flags) = parse_u8(input)?;
974  #[allow(clippy::identity_op)]
975  let use_smoothing = (flags & (1 << 0)) != 0;
976  let deblocking = video_deblocking_from_code((flags >> 1) & 0b111)
977    .map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Switch)))?;
978  // Bits [4, 7] are reserved
979  let (input, codec) = parse_videoc_codec(input)?;
980
981  Ok((
982    input,
983    ast::tags::DefineVideoStream {
984      id,
985      frame_count,
986      width,
987      height,
988      use_smoothing,
989      deblocking,
990      codec,
991    },
992  ))
993}
994
995fn parse_do_abc(input: &[u8], has_header: bool) -> NomResult<&[u8], ast::tags::DoAbc> {
996  let (input, header) = if has_header {
997    let (input, flags) = parse_le_u32(input)?;
998    let (input, name) = parse_c_string(input)?;
999    (input, Some(AbcHeader { flags, name }))
1000  } else {
1001    (input, None)
1002  };
1003  let (input, data) = parse_bytes(input)?;
1004  Ok((input, ast::tags::DoAbc { header, data }))
1005}
1006
1007pub fn parse_do_action(input: &[u8]) -> NomResult<&[u8], ast::tags::DoAction> {
1008  let (input, actions) = parse_bytes(input)?;
1009  Ok((input, ast::tags::DoAction { actions }))
1010}
1011
1012pub fn parse_do_init_action(input: &[u8]) -> NomResult<&[u8], ast::tags::DoInitAction> {
1013  let (input, sprite_id) = parse_le_u16(input)?;
1014  let (input, actions) = (&[][..], input.to_vec());
1015  Ok((input, ast::tags::DoInitAction { sprite_id, actions }))
1016}
1017
1018pub fn parse_enable_debugger(input: &[u8]) -> NomResult<&[u8], ast::tags::EnableDebugger> {
1019  let (input, password) = parse_c_string(input)?;
1020  Ok((input, ast::tags::EnableDebugger { password }))
1021}
1022
1023pub fn parse_enable_debugger2(input: &[u8]) -> NomResult<&[u8], ast::tags::EnableDebugger> {
1024  let (input, _) = skip(2usize)(input)?;
1025  let (input, password) = parse_c_string(input)?;
1026  Ok((input, ast::tags::EnableDebugger { password }))
1027}
1028
1029pub fn parse_enable_telemetry(input: &[u8]) -> NomResult<&[u8], ast::tags::Telemetry> {
1030  use nom::bytes::complete::take;
1031  use nom::combinator::cond;
1032  const HASH_SIZE: usize = 32;
1033  let (input, _) = skip(2usize)(input)?;
1034  let (input, password) = cond(input.len() >= HASH_SIZE, take(HASH_SIZE))(input)?;
1035  Ok((
1036    input,
1037    ast::tags::Telemetry {
1038      password: password.map(|p| p.to_vec()),
1039    },
1040  ))
1041}
1042
1043pub fn parse_export_assets(input: &[u8]) -> NomResult<&[u8], ast::tags::ExportAssets> {
1044  use nom::combinator::map;
1045  use nom::multi::count;
1046  let (input, asset_count) = map(parse_le_u16, usize::from)(input)?;
1047  let (input, assets) = count(parse_named_id, asset_count)(input)?;
1048  Ok((input, ast::tags::ExportAssets { assets }))
1049}
1050
1051pub fn parse_file_attributes_tag(input: &[u8]) -> NomResult<&[u8], ast::tags::FileAttributes> {
1052  let (input, flags) = parse_le_u32(input)?;
1053  #[allow(clippy::identity_op)]
1054  let use_network = (flags & (1 << 0)) != 0;
1055  let use_relative_urls = (flags & (1 << 1)) != 0;
1056  let no_cross_domain_caching = (flags & (1 << 2)) != 0;
1057  let use_as3 = (flags & (1 << 3)) != 0;
1058  let has_metadata = (flags & (1 << 4)) != 0;
1059  let use_gpu = (flags & (1 << 5)) != 0;
1060  let use_direct_blit = (flags & (1 << 6)) != 0;
1061
1062  Ok((
1063    input,
1064    ast::tags::FileAttributes {
1065      use_network,
1066      use_relative_urls,
1067      no_cross_domain_caching,
1068      use_as3,
1069      has_metadata,
1070      use_gpu,
1071      use_direct_blit,
1072    },
1073  ))
1074}
1075
1076pub fn parse_frame_label(input: &[u8]) -> NomResult<&[u8], ast::tags::FrameLabel> {
1077  let (input, name) = parse_c_string(input)?;
1078  let (input, is_anchor) = if input.is_empty() {
1079    (input, false)
1080  } else {
1081    let (input, anchor_flag) = parse_u8(input)?;
1082    (input, anchor_flag != 0)
1083  };
1084
1085  Ok((input, ast::tags::FrameLabel { name, is_anchor }))
1086}
1087
1088pub fn parse_import_assets(input: &[u8]) -> NomResult<&[u8], ast::tags::ImportAssets> {
1089  use nom::combinator::map;
1090  use nom::multi::count;
1091  let (input, url) = parse_c_string(input)?;
1092  let (input, asset_count) = map(parse_le_u16, usize::from)(input)?;
1093  let (input, assets) = count(parse_named_id, asset_count)(input)?;
1094  Ok((input, ast::tags::ImportAssets { url, assets }))
1095}
1096
1097#[allow(unused_variables)]
1098pub fn parse_import_assets2(input: &[u8]) -> NomResult<&[u8], ast::tags::ImportAssets> {
1099  use nom::combinator::map;
1100  use nom::multi::count;
1101
1102  let (input, url) = parse_c_string(input)?;
1103  let (input, _) = skip(2usize)(input)?;
1104  let (input, asset_count) = map(parse_le_u16, usize::from)(input)?;
1105  let (input, assets) = count(parse_named_id, asset_count)(input)?;
1106  Ok((input, ast::tags::ImportAssets { url, assets }))
1107}
1108
1109pub fn parse_metadata(input: &[u8]) -> NomResult<&[u8], ast::tags::Metadata> {
1110  let (input, metadata) = parse_c_string(input)?;
1111  Ok((input, ast::tags::Metadata { metadata }))
1112}
1113
1114pub fn parse_place_object(input: &[u8]) -> NomResult<&[u8], ast::tags::PlaceObject> {
1115  use nom::combinator::{cond, map};
1116
1117  let (input, character_id) = parse_le_u16(input)?;
1118  let (input, depth) = parse_le_u16(input)?;
1119  let (input, matrix) = parse_matrix(input)?;
1120  let (input, color_transform) = cond(
1121    !input.is_empty(),
1122    map(parse_color_transform, |color_transform| ast::ColorTransformWithAlpha {
1123      red_mult: color_transform.red_mult,
1124      green_mult: color_transform.green_mult,
1125      blue_mult: color_transform.blue_mult,
1126      alpha_mult: ::swf_fixed::Sfixed8P8::ONE,
1127      red_add: color_transform.red_add,
1128      green_add: color_transform.green_add,
1129      blue_add: color_transform.blue_add,
1130      alpha_add: 0,
1131    }),
1132  )(input)?;
1133
1134  Ok((
1135    input,
1136    ast::tags::PlaceObject {
1137      is_update: false,
1138      depth,
1139      character_id: Option::Some(character_id),
1140      matrix: Option::Some(matrix),
1141      color_transform,
1142      ratio: Option::None,
1143      name: Option::None,
1144      class_name: Option::None,
1145      clip_depth: Option::None,
1146      filters: Option::None,
1147      blend_mode: Option::None,
1148      bitmap_cache: Option::None,
1149      visible: Option::None,
1150      background_color: Option::None,
1151      clip_actions: Option::None,
1152    },
1153  ))
1154}
1155
1156pub fn parse_place_object2(input: &[u8], swf_version: u8) -> NomResult<&[u8], ast::tags::PlaceObject> {
1157  use nom::combinator::cond;
1158
1159  let (input, flags) = parse_u8(input)?;
1160  #[allow(clippy::identity_op)]
1161  let is_update = (flags & (1 << 0)) != 0;
1162  let has_character_id = (flags & (1 << 1)) != 0;
1163  let has_matrix = (flags & (1 << 2)) != 0;
1164  let has_color_transform = (flags & (1 << 3)) != 0;
1165  let has_ratio = (flags & (1 << 4)) != 0;
1166  let has_name = (flags & (1 << 5)) != 0;
1167  let has_clip_depth = (flags & (1 << 6)) != 0;
1168  let has_clip_actions = (flags & (1 << 7)) != 0;
1169  let (input, depth) = parse_le_u16(input)?;
1170  let (input, character_id) = cond(has_character_id, parse_le_u16)(input)?;
1171  let (input, matrix) = cond(has_matrix, parse_matrix)(input)?;
1172  let (input, color_transform) = cond(has_color_transform, parse_color_transform_with_alpha)(input)?;
1173  let (input, ratio) = cond(has_ratio, parse_le_u16)(input)?;
1174  let (input, name) = cond(has_name, parse_c_string)(input)?;
1175  let (input, clip_depth) = cond(has_clip_depth, parse_le_u16)(input)?;
1176  let (input, clip_actions) = cond(has_clip_actions, |i| parse_clip_actions_string(i, swf_version >= 6))(input)?;
1177
1178  Ok((
1179    input,
1180    ast::tags::PlaceObject {
1181      is_update,
1182      depth,
1183      character_id,
1184      matrix,
1185      color_transform,
1186      ratio,
1187      name,
1188      class_name: None,
1189      clip_depth,
1190      filters: None,
1191      blend_mode: None,
1192      bitmap_cache: None,
1193      visible: None,
1194      background_color: None,
1195      clip_actions,
1196    },
1197  ))
1198}
1199
1200pub fn parse_place_object3(input: &[u8], swf_version: u8) -> NomResult<&[u8], ast::tags::PlaceObject> {
1201  use nom::combinator::{cond, map};
1202
1203  let (input, flags) = parse_le_u16(input)?;
1204  #[allow(clippy::identity_op)]
1205  let is_update = (flags & (1 << 0)) != 0;
1206  let has_character_id = (flags & (1 << 1)) != 0;
1207  let has_matrix = (flags & (1 << 2)) != 0;
1208  let has_color_transform = (flags & (1 << 3)) != 0;
1209  let has_ratio = (flags & (1 << 4)) != 0;
1210  let has_name = (flags & (1 << 5)) != 0;
1211  let has_clip_depth = (flags & (1 << 6)) != 0;
1212  let has_clip_actions = (flags & (1 << 7)) != 0;
1213  let has_filters = (flags & (1 << 8)) != 0;
1214  let has_blend_mode = (flags & (1 << 9)) != 0;
1215  let has_cache_hint = (flags & (1 << 10)) != 0;
1216  let has_class_name = (flags & (1 << 11)) != 0;
1217  let has_image = (flags & (1 << 12)) != 0;
1218  let has_visibility = (flags & (1 << 13)) != 0;
1219  let has_background_color = (flags & (1 << 14)) != 0;
1220  // Skip bit 15
1221
1222  let (input, depth) = parse_le_u16(input)?;
1223  let (input, class_name) = cond(has_class_name || (has_image && has_character_id), parse_c_string)(input)?;
1224  let (input, character_id) = cond(has_character_id, parse_le_u16)(input)?;
1225  let (input, matrix) = cond(has_matrix, parse_matrix)(input)?;
1226  let (input, color_transform) = cond(has_color_transform, parse_color_transform_with_alpha)(input)?;
1227  let (input, ratio) = cond(has_ratio, parse_le_u16)(input)?;
1228  let (input, name) = cond(has_name, parse_c_string)(input)?;
1229  let (input, clip_depth) = cond(has_clip_depth, parse_le_u16)(input)?;
1230  let (input, filters) = cond(has_filters, parse_filter_list)(input)?;
1231  let (input, blend_mode) = cond(has_blend_mode, parse_blend_mode)(input)?;
1232  let (input, use_bitmap_cache) = cond(has_cache_hint, map(parse_u8, |x| x != 0))(input)?;
1233  let (input, is_visible) = cond(has_visibility, map(parse_u8, |x| x != 0))(input)?;
1234  // TODO(demurgos): Check if it is RGBA or ARGB
1235  let (input, background_color) = cond(has_background_color, parse_straight_s_rgba8)(input)?;
1236  let (input, clip_actions) = cond(has_clip_actions, |i| parse_clip_actions_string(i, swf_version >= 6))(input)?;
1237
1238  Ok((
1239    input,
1240    ast::tags::PlaceObject {
1241      is_update,
1242      depth,
1243      character_id,
1244      matrix,
1245      color_transform,
1246      ratio,
1247      name,
1248      class_name,
1249      clip_depth,
1250      filters,
1251      blend_mode,
1252      bitmap_cache: use_bitmap_cache,
1253      visible: is_visible,
1254      background_color,
1255      clip_actions,
1256    },
1257  ))
1258}
1259
1260fn parse_protect(input: &[u8]) -> NomResult<&[u8], ast::tags::Protect> {
1261  let (input, password) = parse_block_c_string(input)?;
1262  Ok((input, ast::tags::Protect { password }))
1263}
1264
1265pub fn parse_remove_object(input: &[u8]) -> NomResult<&[u8], ast::tags::RemoveObject> {
1266  use nom::combinator::map;
1267  let (input, character_id) = map(parse_le_u16, Some)(input)?;
1268  let (input, depth) = parse_le_u16(input)?;
1269  Ok((input, ast::tags::RemoveObject { character_id, depth }))
1270}
1271
1272pub fn parse_remove_object2(input: &[u8]) -> NomResult<&[u8], ast::tags::RemoveObject> {
1273  let (input, depth) = parse_le_u16(input)?;
1274  Ok((
1275    input,
1276    ast::tags::RemoveObject {
1277      character_id: None,
1278      depth,
1279    },
1280  ))
1281}
1282
1283pub fn parse_script_limits(input: &[u8]) -> NomResult<&[u8], ast::tags::ScriptLimits> {
1284  let (input, max_recursion_depth) = parse_le_u16(input)?;
1285  let (input, script_timeout) = parse_le_u16(input)?;
1286  Ok((
1287    input,
1288    ast::tags::ScriptLimits {
1289      max_recursion_depth,
1290      script_timeout,
1291    },
1292  ))
1293}
1294
1295pub fn parse_set_background_color_tag(input: &[u8]) -> NomResult<&[u8], ast::tags::SetBackgroundColor> {
1296  let (input, color) = parse_s_rgb8(input)?;
1297  Ok((input, ast::tags::SetBackgroundColor { color }))
1298}
1299
1300pub fn parse_set_tab_index(input: &[u8]) -> NomResult<&[u8], ast::tags::SetTabIndex> {
1301  let (input, depth) = parse_le_u16(input)?;
1302  let (input, index) = parse_le_u16(input)?;
1303  Ok((input, ast::tags::SetTabIndex { depth, index }))
1304}
1305
1306fn parse_sound_stream_block(input: &[u8]) -> NomResult<&[u8], ast::tags::SoundStreamBlock> {
1307  let (input, data) = parse_bytes(input)?;
1308  Ok((input, ast::tags::SoundStreamBlock { data }))
1309}
1310
1311fn parse_sound_stream_head(input: &[u8]) -> NomResult<&[u8], ast::tags::SoundStreamHead> {
1312  parse_sound_stream_head_any(input)
1313}
1314
1315fn parse_sound_stream_head2(input: &[u8]) -> NomResult<&[u8], ast::tags::SoundStreamHead> {
1316  parse_sound_stream_head_any(input)
1317}
1318
1319fn parse_sound_stream_head_any(input: &[u8]) -> NomResult<&[u8], ast::tags::SoundStreamHead> {
1320  use nom::combinator::cond;
1321  let (input, flags) = parse_le_u16(input)?;
1322  #[allow(clippy::identity_op)]
1323  let playback_sound_type = if (flags & (1 << 0)) != 0 {
1324    ast::SoundType::Stereo
1325  } else {
1326    ast::SoundType::Mono
1327  };
1328  let playback_sound_size = if (flags & (1 << 1)) != 0 {
1329    ast::SoundSize::SoundSize16
1330  } else {
1331    ast::SoundSize::SoundSize8
1332  };
1333  let playback_sound_rate = sound_rate_from_code(((flags >> 2) & 0b11) as u8)
1334    .map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Switch)))?;
1335  // Bits [4, 7] are reserved
1336  let stream_sound_type = if (flags & (1 << 8)) != 0 {
1337    ast::SoundType::Stereo
1338  } else {
1339    ast::SoundType::Mono
1340  };
1341  let stream_sound_size = if (flags & (1 << 9)) != 0 {
1342    ast::SoundSize::SoundSize16
1343  } else {
1344    ast::SoundSize::SoundSize8
1345  };
1346  let stream_sound_rate = sound_rate_from_code(((flags >> 10) & 0b11) as u8)
1347    .map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Switch)))?;
1348  let stream_format = audio_coding_format_from_code(((flags >> 12) & 0b1111) as u8)
1349    .map_err(|_| nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Switch)))?;
1350  let (input, stream_sample_count) = parse_le_u16(input)?;
1351  let (input, latency_seek) = cond(stream_format == ast::AudioCodingFormat::Mp3, parse_le_i16)(input)?;
1352  Ok((
1353    input,
1354    ast::tags::SoundStreamHead {
1355      playback_sound_type,
1356      playback_sound_size,
1357      playback_sound_rate,
1358      stream_sound_type,
1359      stream_sound_size: if is_uncompressed_audio_coding_format(stream_format) {
1360        stream_sound_size
1361      } else {
1362        ast::SoundSize::SoundSize16
1363      },
1364      stream_sound_rate,
1365      stream_format,
1366      stream_sample_count,
1367      latency_seek,
1368    },
1369  ))
1370}
1371
1372pub fn parse_start_sound(input: &[u8]) -> NomResult<&[u8], ast::tags::StartSound> {
1373  let (input, sound_id) = parse_le_u16(input)?;
1374  let (input, sound_info) = parse_sound_info(input)?;
1375  Ok((input, ast::tags::StartSound { sound_id, sound_info }))
1376}
1377
1378pub fn parse_start_sound2(input: &[u8]) -> NomResult<&[u8], ast::tags::StartSound2> {
1379  let (input, sound_class_name) = parse_c_string(input)?;
1380  let (input, sound_info) = parse_sound_info(input)?;
1381  Ok((
1382    input,
1383    ast::tags::StartSound2 {
1384      sound_class_name,
1385      sound_info,
1386    },
1387  ))
1388}
1389
1390pub fn parse_symbol_class(input: &[u8]) -> NomResult<&[u8], ast::tags::SymbolClass> {
1391  use nom::combinator::map;
1392  use nom::multi::count;
1393  let (input, symbol_count) = map(parse_le_u16, usize::from)(input)?;
1394  let (input, symbols) = count(parse_named_id, symbol_count)(input)?;
1395  Ok((input, ast::tags::SymbolClass { symbols }))
1396}
1397
1398pub fn parse_video_frame(input: &[u8]) -> NomResult<&[u8], ast::tags::VideoFrame> {
1399  let (input, video_id) = parse_le_u16(input)?;
1400  let (input, frame) = parse_le_u16(input)?;
1401  let (input, packet) = parse_bytes(input)?;
1402  Ok((
1403    input,
1404    ast::tags::VideoFrame {
1405      video_id,
1406      frame,
1407      packet,
1408    },
1409  ))
1410}
1411
1412#[cfg(test)]
1413mod tests {
1414  use super::parse_tag;
1415  use std::path::Path;
1416  use swf_types::Tag;
1417  use test_generator::test_resources;
1418
1419  #[test_resources("../tests/tags/*/*/")]
1420  #[test_resources("../tests/local-tags/*/*/")]
1421  fn test_parse_tag(path: &str) {
1422    let path: &Path = Path::new(path);
1423    let name = path
1424      .components()
1425      .last()
1426      .unwrap()
1427      .as_os_str()
1428      .to_str()
1429      .expect("Failed to retrieve sample name");
1430    let input_path = path.join("input.bytes");
1431    let input_bytes: Vec<u8> = ::std::fs::read(input_path).expect("Failed to read input");
1432
1433    let swf_version: u8 = match name {
1434      "po2-swf5" => 5,
1435      _ => 10,
1436    };
1437
1438    let (remaining_bytes, actual_value) = parse_tag(&input_bytes, swf_version);
1439
1440    let expected_path = path.join("value.json");
1441    let expected_file = ::std::fs::File::open(&expected_path).expect("Failed to open expected value file");
1442    let expected_reader = ::std::io::BufReader::new(expected_file);
1443    let expected_value = serde_json_v8::from_reader::<_, Tag>(expected_reader).expect("Failed to read AST");
1444
1445    assert_eq!(actual_value, Some(expected_value));
1446    assert_eq!(remaining_bytes, &[] as &[u8], "Assert all input is consumed");
1447  }
1448
1449  //  #[test]
1450  //  fn test_fuzzing() {
1451  //    let artifact: &[u8] = include_bytes!("../../fuzz/artifacts/tag/crash-03fe96da7c46bbe615f27f3e6dbe7f481246e67f");
1452  //    let (swf_version, input_bytes) = artifact.split_first().unwrap();
1453  //    let _ = parse_tag(input_bytes, *swf_version);
1454  //  }
1455}