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
35pub 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 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 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)?; 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 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 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 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 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 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)?; 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 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)?; 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 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
533fn 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 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 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 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 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 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 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 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 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 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 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 }