1# Copyright 2013-2014 Facundo Batista
2#
3# This program is free software: you can redistribute it and/or modify it
4# under the terms of the GNU General Public License version 3, as published
5# by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful, but
8# WITHOUT ANY WARRANTY; without even the implied warranties of
9# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
10# PURPOSE.  See the GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License along
13# with this program.  If not, see <http://www.gnu.org/licenses/>.
14#
15# For further info, check  http://github.com/facundobatista/yaswfp
16
17"""Parse a SWF file and expose all its internals.
18
19This follows the SWF FILE FORMAT SPECIFICATION VERSION 19 which is not
20included in this project for your easier finding because Adobe forbids
21the spec distribution.
22
23The attributes names are CamelCase to match as close as possible the
24spec.
25
26Note: not all the spec is covered (work in progress!), there's a flag
27in the SWFParser to change the behaviour when an still-not-done object
28is found.
29"""
30
31import collections
32import io
33import zlib
34
35from .helpers import (
36    BitConsumer,
37    ReadQuantityController,
38    unpack_si16,
39    unpack_ui16,
40    unpack_ui32,
41    unpack_ui8,
42    unpack_fixed8,
43    unpack_fixed16,
44    unpack_float16,
45    unpack_float,
46    unpack_double,
47)
48
49VERSION = "0.9.3"
50
51# name of each tag (as a dict, not a list, for easier human consumption)
52TAG_NAMES = {
53    0: "End",
54    1: "ShowFrame",
55    2: "DefineShape",
56    4: "PlaceObject",
57    5: "RemoveObject",
58    6: "DefineBits",
59    7: "DefineButton",
60    8: "JPEGTables",
61    9: "SetBackgroundColor",
62    10: "DefineFont",
63    11: "DefineText",
64    12: "DoAction",
65    13: "DefineFontInfo",
66    14: "DefineSound",
67    15: "StartSound",
68    17: "DefineButtonSound",
69    18: "SoundStreamHead",
70    19: "SoundStreamBlock",
71    20: "DefineBitsLossless",
72    21: "DefineBitsJPEG2",
73    22: "DefineShape2",
74    23: "DefineButtonCxform",
75    24: "Protect",
76    26: "PlaceObject2",
77    28: "RemoveObject2",
78    32: "DefineShape3",
79    33: "DefineText2",
80    34: "DefineButton2",
81    35: "DefineBitsJPEG3",
82    36: "DefineBitsLossless2",
83    37: "DefineEditText",
84    39: "DefineSprite",
85    43: "FrameLabel",
86    45: "SoundStreamHead2",
87    46: "DefineMorphShape",
88    48: "DefineFont2",
89    56: "ExportAssets",
90    57: "ImportAssets",
91    58: "EnableDebugger",
92    59: "DoInitAction",
93    60: "DefineVideoStream",
94    61: "VideoFrame",
95    62: "DefineFontInfo2",
96    64: "EnableDebugger2",
97    65: "ScriptLimits",
98    66: "SetTabIndex",
99    69: "FileAttributes",
100    70: "PlaceObject3",
101    71: "ImportAssets2",
102    73: "DefineFontAlignZones",
103    74: "CSMTextSettings",
104    75: "DefineFont3",
105    76: "SymbolClass",
106    77: "Metadata",
107    78: "DefineScalingGrid",
108    82: "DoABC",
109    83: "DefineShape4",
110    84: "DefineMorphShape2",
111    86: "DefineSceneAndFrameLabelData",
112    87: "DefineBinaryData",
113    88: "DefineFontName",
114    89: "StartSound2",
115    90: "DefineBitsJPEG4",
116    91: "DefineFont4",
117}
118
119LANGCODES = {
120    1: "Latin",
121    2: "Japanese",
122    3: "Korean",
123    4: "Simplified Chinese",
124    5: "Traditional Chinese",
125}
126
127ACTION_NAMES = {
128    0x04: 'ActionNextFrame',
129    0x05: 'ActionPrevFrame',
130    0x06: 'ActionPlay',
131    0x07: 'ActionStop',
132    0x08: 'ActionToggleQualty',
133    0x09: 'ActionStopSounds',
134    0x0A: 'ActionAdd',
135    0x0B: 'ActionSubtract',
136    0x0C: 'ActionMultiply',
137    0x0D: 'ActionDivide',
138    0x0E: 'ActionEquals',
139    0x0F: 'ActionLess',
140    0x10: 'ActionAnd',
141    0x11: 'ActionOr',
142    0x12: 'ActionNot',
143    0x13: 'ActionStringEquals',
144    0x14: 'ActionStringLength',
145    0x15: 'ActionStringExtract',
146    0x17: 'ActionPop',
147    0x18: 'ActionToInteger',
148    0x1C: 'ActionGetVariable',
149    0x1D: 'ActionSetVariable',
150    0x20: 'ActionSetTarget2',
151    0x21: 'ActionStringAdd',
152    0x22: 'ActionGetProperty',
153    0x23: 'ActionSetProperty',
154    0x24: 'ActionCloneSprite',
155    0x25: 'ActionRemoveSprite',
156    0x26: 'ActionTrace',
157    0x27: 'ActionStartDrag',
158    0x28: 'ActionEndDrag',
159    0x29: 'ActionStringLess',
160    0x2A: 'ActionThrow',
161    0x2B: 'ActionCastOp',
162    0x2C: 'ActionImplementsOp',
163    0x30: 'ActionRandomNumber',
164    0x31: 'ActionMBStringLength',
165    0x32: 'ActionCharToAscii',
166    0x33: 'ActionAsciiToChar',
167    0x34: 'ActionGetTime',
168    0x35: 'ActionMBStringExtract',
169    0x36: 'ActionMBCharToAscii',
170    0x37: 'ActionMBAsciiToChar',
171    0x3A: 'ActionDelete',
172    0x3B: 'ActionDelete2',
173    0x3C: 'ActionDefineLocal',
174    0x3D: 'ActionCallFunction',
175    0x3E: 'ActionReturn',
176    0x3F: 'ActionModulo',
177    0x40: 'ActionNewObject',
178    0x41: 'ActionDefineLocal2',
179    0x42: 'ActionInitArray',
180    0x43: 'ActionInitObject',
181    0x44: 'ActionTypeOf',
182    0x45: 'ActionTargetPath',
183    0x46: 'ActionEnumerate',
184    0x47: 'ActionAdd2',
185    0x48: 'ActionLess2',
186    0x49: 'ActionEquals2',
187    0x4A: 'ActionToNumber',
188    0x4B: 'ActionToString',
189    0x4C: 'ActionPushDuplicate',
190    0x4D: 'ActionStackSwap',
191    0x4E: 'ActionGetMember',
192    0x4F: 'ActionSetMember',
193    0x50: 'ActionIncrement',
194    0x51: 'ActionDecrement',
195    0x52: 'ActionCallMethod',
196    0x53: 'ActionNewMethod',
197    0x54: 'ActionInstanceOf',
198    0x55: 'ActionEnumerate2',
199    0x60: 'ActionBitAnd',
200    0x61: 'ActionBitOr',
201    0x62: 'ActionBitXor',
202    0x63: 'ActionBitLShift',
203    0x64: 'ActionBitRShift',
204    0x65: 'ActionBitURShift',
205    0x66: 'ActionStrictEquals',
206    0x67: 'ActionGreater',
207    0x68: 'ActionStringGreater',
208    0x69: 'ActionExtends',
209    0x81: 'ActionGotoFrame',
210    0x83: 'ActionGetURL',
211    0x87: 'ActionStoreRegister',
212    0x88: 'ActionConstantPool',
213    0x8A: 'ActionWaitForFrame',
214    0x8B: 'ActionSetTarget',
215    0x8C: 'ActionGoToLabel',
216    0x8D: 'ActionWaitForFrame2',
217    0x8E: 'ActionDefineFunction2',
218    0x8F: 'ActionTry',
219    0x94: 'ActionWith',
220    0x96: 'ActionPush',
221    0x99: 'ActionJump',
222    0x9A: 'ActionGetURL2',
223    0x9B: 'ActionDefineFunction',
224    0x9D: 'ActionIf',
225    0x9E: 'ActionCall',
226    0x9F: 'ActionGotoFrame2',
227}
228
229
230def _str(obj):
231    """Show nicely the generic object received."""
232    values = []
233    for name in obj._attribs:
234        val = getattr(obj, name)
235        if isinstance(val, str):
236            val = repr(val)
237        val = str(val) if len(str(val)) < 10 else "(...)"
238        values.append((name, val))
239    values = ", ".join("{}={}".format(k, v) for k, v in values)
240    return "{}({})".format(obj.__class__.__name__, values)
241
242
243def _repr(obj):
244    """Show the received object as precise as possible."""
245    vals = ", ".join("{}={!r}".format(
246        name, getattr(obj, name)) for name in obj._attribs)
247    if vals:
248        t = "{}(name={}, {})".format(obj.__class__.__name__, obj.name, vals)
249    else:
250        t = "{}(name={})".format(obj.__class__.__name__, obj.name)
251    return t
252
253
254class SWFObject:
255    """A super class for all the objects created here."""
256
257    def __init__(self):
258        self._attribs = []
259
260    def __setattr__(self, name, value):
261        if name != "_attribs":
262            if name not in self._attribs:
263                self._attribs.append(name)
264        super(SWFObject, self).__setattr__(name, value)
265
266
267def _make_object(name):
268    """Create a generic object for the tags."""
269    klass = type(name, (SWFObject,),
270                 {'__str__': _str, '__repr__': _repr, 'name': name})
271    return klass()
272
273
274class SWFParser:
275    """Read (at a byte or bit level) the SWF structure from a fileobject.
276
277    When the parser finds a structure that still can't process (because more
278    programming is needed), will just return an UnknownObject object with
279    the unparsed bytes, or will raise an exception if you set
280    the unknown_alert flag::
281
282        SWFParser.unknown_alert = True
283    """
284
285    unknown_alert = False
286
287    def __init__(self, src):
288        self._src = src
289        self._version = None
290        self._last_defined_glyphs_quantity = None
291        self.header = self._get_header()
292        self.tags = self._process_tags()
293
294    def _get_header(self):
295        """Parse the SWF header."""
296        fh = self._src
297        obj = _make_object("Header")
298
299        # first part of the header
300        obj.Signature = sign = "".join(chr(unpack_ui8(fh)) for _ in range(3))
301        obj.Version = self._version = unpack_ui8(fh)
302        obj.FileLength = file_length = unpack_ui32(fh)
303
304        # deal with compressed content
305        if sign[0] == 'C':
306            uncompressed = zlib.decompress(fh.read())
307            if len(uncompressed) + 8 != file_length:
308                raise ValueError("Problems dealing with compressed content")
309            fh = self._src = io.BytesIO(uncompressed)
310
311        # second part of the header
312        obj.FrameSize = self._get_struct_rect()
313        obj.FrameRate = unpack_ui16(fh)
314        obj.FrameCount = unpack_ui16(fh)
315        return obj
316
317    def _process_tags(self):
318        """Get a sequence of tags."""
319        tags = []
320
321        while True:
322            tag_bf = unpack_ui16(self._src)
323            tag_type = tag_bf >> 6   # upper 10 bits
324            if tag_type == 0:
325                # the end
326                break
327            tag_len = tag_bf & 0x3f  # last 6 bits
328            if tag_len == 0x3f:
329                # the length is the next four bytes!
330                tag_len = unpack_ui32(self._src)
331
332            try:
333                tag_name = TAG_NAMES[tag_type]
334            except KeyError:
335                # malformed SWF, create and unknown object with malformed tag
336                tag_payload = self._src.read(tag_len)
337                _dict = {
338                    '__str__': _repr,
339                    '__repr__': _repr,
340                    'name': 'UnspecifiedObject(tag={!r})'.format(tag_type),
341                }
342                tag = type("UnknownObject", (SWFObject,), _dict)()
343                tag.raw_payload = tag_payload
344                tags.append(tag)
345                continue
346
347            try:
348                tag_meth = getattr(self, "_handle_tag_" + tag_name.lower())
349            except AttributeError:
350                if self.unknown_alert:
351                    raise ValueError("Unknown tag: " + repr(tag_name))
352
353                tag_payload = self._src.read(tag_len)
354                _dict = {'__str__': _repr, '__repr__': _repr, 'name': tag_name}
355                tag = type("UnknownObject", (SWFObject,), _dict)()
356                tag.raw_payload = tag_payload
357                tags.append(tag)
358                continue
359
360            # we know the tag type, and have the handler, let's process it
361            prev_pos = self._src.tell()
362            self._src.guard = tag_len
363            try:
364                with ReadQuantityController(self._src, tag_len):
365                    tag = tag_meth()
366                assert tag is not None, tag_name
367            except ValueError:
368                # an attempt to read too much happened; create a failing
369                # object with the raw payload
370                self._src.guard = None
371                self._src.seek(prev_pos)
372                tag_payload = self._src.read(tag_len)
373                _dict = {'__str__': _repr, '__repr__': _repr, 'name': tag_name}
374                tag = type("FailingObject", (SWFObject,), _dict)()
375                tag.raw_payload = tag_payload
376            tags.append(tag)
377        return tags
378
379    def _handle_tag_definebits(self):
380        """Handle the DefineBits tag."""
381        obj = _make_object("DefineBits")
382        obj.CharacterID = unpack_ui16(self._src)
383        assert self._src.read(2) == b'\xFF\xD8'  # SOI marker
384        eoimark1 = eoimark2 = None
385        allbytes = []
386        while not (eoimark1 == b'\xFF' and eoimark2 == b'\xD9'):
387            newbyte = self._src.read(1)
388            allbytes.append(newbyte)
389            eoimark1 = eoimark2
390            eoimark2 = newbyte
391
392        # concatenate everything, removing the end mark
393        obj.JPEGData = b"".join(allbytes)
394        return obj
395
396    def _handle_tag_definebitsjpeg2(self):
397        """Handle the DefineBitsJPEG2 tag."""
398        obj = _make_object("DefineBitsJPEG2")
399        obj.CharacterID = unpack_ui16(self._src)
400        assert self._src.read(2) == b'\xFF\xD8'  # SOI marker
401        eoimark1 = eoimark2 = None
402        allbytes = []
403        while not (eoimark1 == b'\xFF' and eoimark2 == b'\xD9'):
404            newbyte = self._src.read(1)
405            allbytes.append(newbyte)
406            eoimark1 = eoimark2
407            eoimark2 = newbyte
408
409        # concatenate everything, removing the end mark
410        obj.ImageData = b"".join(allbytes)
411        return obj
412
413    def _generic_definetext_parser(self, obj, rgb_struct):
414        """Generic parser for the DefineTextN tags."""
415        obj.CharacterID = unpack_ui16(self._src)
416        obj.TextBounds = self._get_struct_rect()
417        obj.TextMatrix = self._get_struct_matrix()
418        obj.GlyphBits = glyph_bits = unpack_ui8(self._src)
419        obj.AdvanceBits = advance_bits = unpack_ui8(self._src)
420
421        # textrecords
422        obj.TextRecords = records = []
423        while True:
424            endofrecords_flag = unpack_ui8(self._src)
425            if endofrecords_flag == 0:
426                # all done
427                obj.EndOfRecordsFlag = 0
428                break
429
430            # we have a TEXTRECORD, let's go back the 8 bits and set the obj
431            self._src.seek(-1, io.SEEK_CUR)
432            record = _make_object("TextRecord")
433            records.append(record)
434
435            bc = BitConsumer(self._src)
436            record.TextRecordType = bc.u_get(1)
437            record.StyleFlagsReserved = bc.u_get(3)
438            record.StyleFlagsHasFont = bc.u_get(1)
439            record.StyleFlagsHasColor = bc.u_get(1)
440            record.StyleFlagsHasYOffset = bc.u_get(1)
441            record.StyleFlagsHasXOffset = bc.u_get(1)
442
443            if record.StyleFlagsHasFont:
444                record.FontID = unpack_ui16(self._src)
445            if record.StyleFlagsHasColor:
446                record.TextColor = rgb_struct()
447            if record.StyleFlagsHasXOffset:
448                record.XOffset = unpack_si16(self._src)
449            if record.StyleFlagsHasYOffset:
450                record.YOffset = unpack_si16(self._src)
451            if record.StyleFlagsHasFont:
452                record.TextHeight = unpack_ui16(self._src)
453
454            record.GlyphCount = unpack_ui8(self._src)
455            bc = BitConsumer(self._src)
456            record.GlyphEntries = glyphs = []
457            for _ in range(record.GlyphCount):
458                glyph = _make_object("GlyphEntry")
459                glyphs.append(glyph)
460                glyph.GlyphIndex = bc.u_get(glyph_bits)
461                glyph.GlyphAdvance = bc.u_get(advance_bits)
462
463    def _handle_tag_definetext(self):
464        """Handle the DefineText tag."""
465        obj = _make_object("DefineText")
466        self._generic_definetext_parser(obj, self._get_struct_rgb)
467        return obj
468
469    def _handle_tag_definetext2(self):
470        """Handle the DefineText2 tag."""
471        obj = _make_object("DefineText2")
472        self._generic_definetext_parser(obj, self._get_struct_rgba)
473        return obj
474
475    def _handle_tag_defineedittext(self):
476        """Handle the DefineEditText tag."""
477        obj = _make_object("DefineEditText")
478        obj.CharacterID = unpack_ui16(self._src)
479        obj.Bounds = self._get_struct_rect()
480
481        bc = BitConsumer(self._src)
482        obj.HasText = bc.u_get(1)
483        obj.WordWrap = bc.u_get(1)
484        obj.Multiline = bc.u_get(1)
485        obj.Password = bc.u_get(1)
486        obj.ReadOnly = bc.u_get(1)
487        obj.HasTextColor = bc.u_get(1)
488        obj.HasMaxLength = bc.u_get(1)
489        obj.HasFont = bc.u_get(1)
490        obj.HasFontClass = bc.u_get(1)
491        obj.AutoSize = bc.u_get(1)
492        obj.HasLayout = bc.u_get(1)
493        obj.NoSelect = bc.u_get(1)
494        obj.Border = bc.u_get(1)
495        obj.WasStatic = bc.u_get(1)
496        obj.HTML = bc.u_get(1)
497        obj.UseOutlines = bc.u_get(1)
498
499        if obj.HasFont:
500            obj.FontID = unpack_ui16(self._src)
501        if obj.HasFontClass:
502            obj.FontClass = self._get_struct_string()
503        if obj.HasFont:
504            obj.FontHeight = unpack_ui16(self._src)
505        if obj.HasTextColor:
506            obj.TextColor = self._get_struct_rgba()
507        if obj.HasMaxLength:
508            obj.MaxLength = unpack_ui16(self._src)
509        if obj.HasLayout:
510            obj.Align = unpack_ui8(self._src)
511            obj.LeftMargin = unpack_ui16(self._src)
512            obj.RightMargin = unpack_ui16(self._src)
513            obj.Indent = unpack_ui16(self._src)
514            obj.Leading = unpack_ui16(self._src)
515
516        obj.VariableName = self._get_struct_string()
517        if obj.HasText:
518            obj.InitialText = self._get_struct_string()
519        return obj
520
521    def _generic_placeobject_parser(self, obj, version):
522        """A generic parser for several PlaceObjectX."""
523        bc = BitConsumer(self._src)
524        obj.PlaceFlagHasClipActions = bc.u_get(1)
525        obj.PlaceFlagHasClipDepth = bc.u_get(1)
526        obj.PlaceFlagHasName = bc.u_get(1)
527        obj.PlaceFlagHasRatio = bc.u_get(1)
528        obj.PlaceFlagHasColorTransform = bc.u_get(1)
529        obj.PlaceFlagHasMatrix = bc.u_get(1)
530        obj.PlaceFlagHasCharacter = bc.u_get(1)
531        obj.PlaceFlagMove = bc.u_get(1)
532
533        if version == 3:
534            obj.Reserved = bc.u_get(1)
535            obj.PlaceFlagOpaqueBackground = bc.u_get(1)
536            obj.PlaceFlagHasVisible = bc.u_get(1)
537            obj.PlaceFlagHasImage = bc.u_get(1)
538            obj.PlaceFlagHasClassName = bc.u_get(1)
539            obj.PlaceFlagHasCacheAsBitmap = bc.u_get(1)
540            obj.PlaceFlagHasBlendMode = bc.u_get(1)
541            obj.PlaceFlagHasFilterList = bc.u_get(1)
542
543        obj.Depth = unpack_ui16(self._src)
544
545        if version == 3:
546            if obj.PlaceFlagHasClassName or (
547                    obj.PlaceFlagHasImage and obj.PlaceFlagHasCharacter):
548                obj.ClassName = self._get_struct_string()
549
550        if obj.PlaceFlagHasCharacter:
551            obj.CharacterId = unpack_ui16(self._src)
552        if obj.PlaceFlagHasMatrix:
553            obj.Matrix = self._get_struct_matrix()
554        if obj.PlaceFlagHasColorTransform:
555            obj.ColorTransform = self._get_struct_cxformwithalpha()
556        if obj.PlaceFlagHasRatio:
557            obj.Ratio = unpack_ui16(self._src)
558        if obj.PlaceFlagHasName:
559            obj.Name = self._get_struct_string()
560        if obj.PlaceFlagHasClipDepth:
561            obj.ClipDepth = unpack_ui16(self._src)
562
563        if version == 3:
564            if obj.PlaceFlagHasFilterList:
565                obj.SurfaceFilterList = self._get_struct_filterlist()
566            if obj.PlaceFlagHasBlendMode:
567                obj.BlendMode = unpack_ui8(self._src)
568            if obj.PlaceFlagHasCacheAsBitmap:
569                obj.BitmapCache = unpack_ui8(self._src)
570            if obj.PlaceFlagHasVisible:
571                obj.Visible = unpack_ui8(self._src)
572                obj.BackgroundColor = self._get_struct_rgba()
573
574        if obj.PlaceFlagHasClipActions:
575            obj.ClipActions = self._get_struct_clipactions()
576
577    def _handle_tag_placeobject2(self):
578        """Handle the PlaceObject2 tag."""
579        obj = _make_object("PlaceObject2")
580        self._generic_placeobject_parser(obj, 2)
581        return obj
582
583    def _handle_tag_placeobject3(self):
584        """Handle the PlaceObject3 tag."""
585        obj = _make_object("PlaceObject3")
586        self._generic_placeobject_parser(obj, 3)
587        return obj
588
589    def _handle_tag_definesprite(self):
590        """Handle the DefineSprite tag."""
591        obj = _make_object("DefineSprite")
592        obj.CharacterID = unpack_ui16(self._src)
593        obj.FrameCount = unpack_ui16(self._src)
594        tags = self._process_tags()
595        obj.ControlTags = tags
596        return obj
597
598    def _generic_action_parser(self):
599        """Generic parser for Actions."""
600        actions = []
601        while True:
602            action_code = unpack_ui8(self._src)
603            if action_code == 0:
604                break
605
606            action_name = ACTION_NAMES[action_code]
607            if action_code > 128:
608                # have a payload!
609                action_len = unpack_ui16(self._src)
610                try:
611                    action_meth = getattr(
612                        self, "_handle_" + action_name.lower())
613                except AttributeError:
614                    if self.unknown_alert:
615                        raise ValueError(
616                            "Unknown action: " + repr(action_name))
617
618                    action_payload = self._src.read(action_len)
619                    _dict = {'__str__': _repr, '__repr__': _repr,
620                             'name': action_name}
621                    action = type("UnknownAction", (SWFObject,), _dict)()
622                    action.raw_payload = action_payload
623                    actions.append(action)
624                else:
625                    prev_pos = self._src.tell()
626                    for action in action_meth(action_len):
627                        assert action is not None, action_name
628                        actions.append(action)
629
630                    quant_read = self._src.tell() - prev_pos
631                    if quant_read != action_len:
632                        raise RuntimeError(
633                            "Bad bytes consumption by action {!r} handler "
634                            "(did {}, should {})".format(
635                                action_name, quant_read, action_len))
636            else:
637                action = _make_object(action_name)
638                actions.append(action)
639        return actions
640
641    def _handle_tag_doaction(self):
642        """Handle the DoAction tag."""
643        obj = _make_object("DoAction")
644        obj.Actions = self._generic_action_parser()
645        return obj
646
647    def _handle_tag_fileattributes(self):
648        """Handle the FileAttributes tag."""
649        obj = _make_object("FileAttributes")
650        bc = BitConsumer(self._src)
651
652        bc.u_get(1)  # reserved
653        obj.UseDirectBlit = bc.u_get(1)
654        obj.UseGPU = bc.u_get(1)
655        obj.HasMetadata = bc.u_get(1)
656        obj.ActionScript3 = bc.u_get(1)
657        bc.u_get(2)  # reserved
658        obj.UseNetwork = bc.u_get(1)
659        bc.u_get(24)  # reserved
660        return obj
661
662    def _handle_tag_metadata(self):
663        """Handle the Metadata tag."""
664        obj = _make_object("Metadata")
665        obj.Metadata = self._get_struct_string()
666        return obj
667
668    def _handle_tag_setbackgroundcolor(self):
669        """Handle the SetBackgroundColor tag."""
670        obj = _make_object("SetBackgroundColor")
671        obj.BackgroundColor = self._get_struct_rgb()
672        return obj
673
674    def _handle_tag_definesceneandframelabeldata(self):
675        """Handle the DefineSceneAndFrameLabelData tag."""
676        obj = _make_object("DefineSceneAndFrameLabelData")
677        obj.SceneCount = self._get_struct_encodedu32()
678        for i in range(1, obj.SceneCount + 1):
679            setattr(obj, 'Offset{}'.format(i), self._get_struct_encodedu32())
680            setattr(obj, 'Name{}'.format(i), self._get_struct_string())
681        obj.FrameLabelCount = self._get_struct_encodedu32()
682        for i in range(1, obj.FrameLabelCount + 1):
683            setattr(obj, 'FrameNum{}'.format(i), self._get_struct_encodedu32())
684            setattr(obj, 'FrameLabel{}'.format(i), self._get_struct_string())
685        return obj
686
687    def _handle_tag_defineshape4(self):
688        """Handle the DefineShape4 tag."""
689        obj = _make_object("DefineShape4")
690        obj.ShapeId = unpack_ui16(self._src)
691        obj.ShapeBounds = self._get_struct_rect()
692        obj.EdgeBounds = self._get_struct_rect()
693
694        bc = BitConsumer(self._src)
695        bc.u_get(5)  # reserved
696        obj.UsesFillWindingRule = bc.u_get(1)
697        obj.UsesNonScalingStrokes = bc.u_get(1)
698        obj.UsesScalingStrokes = bc.u_get(1)
699        obj.Shapes = self._get_struct_shapewithstyle(4)
700        return obj
701
702    def _handle_tag_definemorphshape2(self):
703        """Handle the DefineMorphShape2 tag."""
704        obj = _make_object("DefineMorphShape2")
705        obj.CharacterId = unpack_ui16(self._src)
706        obj.StartBounds = self._get_struct_rect()
707        obj.EndBounds = self._get_struct_rect()
708        obj.StartEdgeBounds = self._get_struct_rect()
709        obj.EndEdgeBounds = self._get_struct_rect()
710
711        bc = BitConsumer(self._src)
712        bc.u_get(6)  # reserved
713        obj.UsesNonScalingStrokes = bc.u_get(1)
714        obj.UsesScalingStrokes = bc.u_get(1)
715
716        obj.Offset = unpack_ui32(self._src)
717
718        # FIXME: this tag needs more work; I'm skipping some attributes here
719        self._src.read(obj.Offset)
720
721        obj.EndEdges = self._get_struct_shape()
722        return obj
723
724    def _handle_tag_showframe(self):
725        """Handle the ShowFrame tag."""
726        return _make_object("ShowFrame")
727
728    def _handle_tag_removeobject(self):
729        """Handle the RemoveObject tag."""
730        obj = _make_object("RemoveObject")
731        obj.CharacterId = unpack_ui16(self._src)
732        obj.Depth = unpack_ui16(self._src)
733        return obj
734
735    def _handle_tag_removeobject2(self):
736        """Handle the RemoveObject2 tag."""
737        obj = _make_object("RemoveObject2")
738        obj.Depth = unpack_ui16(self._src)
739        return obj
740
741    def _handle_tag_defineshape(self):
742        """Handle the DefineShape tag."""
743        obj = _make_object("DefineShape")
744        obj.ShapeId = unpack_ui16(self._src)
745        obj.ShapeBounds = self._get_struct_rect()
746        obj.Shapes = self._get_struct_shapewithstyle(1)
747        return obj
748
749    def _handle_tag_defineshape2(self):
750        """Handle the DefineShape2 tag."""
751        obj = _make_object("DefineShape2")
752        obj.ShapeId = unpack_ui16(self._src)
753        obj.ShapeBounds = self._get_struct_rect()
754        obj.Shapes = self._get_struct_shapewithstyle(2)
755        return obj
756
757    def _handle_tag_defineshape3(self):
758        """Handle the DefineShape3 tag."""
759        obj = _make_object("DefineShape3")
760        obj.ShapeId = unpack_ui16(self._src)
761        obj.ShapeBounds = self._get_struct_rect()
762        obj.Shapes = self._get_struct_shapewithstyle(3)
763        return obj
764
765    def _generic_definefont_parser(self, obj):
766        """A generic parser for several DefineFontX."""
767        obj.FontID = unpack_ui16(self._src)
768
769        bc = BitConsumer(self._src)
770        obj.FontFlagsHasLayout = bc.u_get(1)
771        obj.FontFlagsShiftJIS = bc.u_get(1)
772        obj.FontFlagsSmallText = bc.u_get(1)
773        obj.FontFlagsANSI = bc.u_get(1)
774        obj.FontFlagsWideOffsets = bc.u_get(1)
775        obj.FontFlagsWideCodes = bc.u_get(1)
776        obj.FontFlagsItalic = bc.u_get(1)
777        obj.FontFlagsBold = bc.u_get(1)
778
779        obj.LanguageCode = self._get_struct_langcode()
780        obj.FontNameLen = unpack_ui8(self._src)
781        obj.FontName = "".join(chr(unpack_ui8(self._src))
782                               for i in range(obj.FontNameLen))
783        if obj.FontName[-1] == '\x00':  # most probably ends in null, clean it
784            obj.FontName = obj.FontName[:-1]
785
786        obj.NumGlyphs = num_glyphs = unpack_ui16(self._src)
787        self._last_defined_glyphs_quantity = num_glyphs
788        getter_wide = unpack_ui32 if obj.FontFlagsWideOffsets else unpack_ui16
789        obj.OffsetTable = [getter_wide(self._src) for _ in range(num_glyphs)]
790        obj.CodeTableOffset = getter_wide(self._src)
791        obj.GlyphShapeTable = [self._get_struct_shape()
792                               for _ in range(num_glyphs)]
793        obj.CodeTable = [unpack_ui16(self._src) for _ in range(num_glyphs)]
794
795        if obj.FontFlagsHasLayout:
796            obj.FontAscent = unpack_ui16(self._src)
797            obj.FontDecent = unpack_ui16(self._src)
798            obj.FontLeading = unpack_ui16(self._src)
799            obj.FontAdvanceTable = [unpack_si16(self._src)
800                                    for _ in range(num_glyphs)]
801            obj.FontBoundsTable = [self._get_struct_rect()
802                                   for _ in range(num_glyphs)]
803            obj.KerningCount = unpack_ui16(self._src)
804            obj.FontKerningTable = [
805                self._get_struct_kerningrecord(obj.FontFlagsWideCodes)
806                for _ in range(obj.KerningCount)]
807
808    def _handle_tag_definefont2(self):
809        """Handle the DefineFont2 tag."""
810        obj = _make_object("DefineFont2")
811        self._generic_definefont_parser(obj)
812        return obj
813
814    def _handle_tag_definefont3(self):
815        """Handle the DefineFont3 tag."""
816        obj = _make_object("DefineFont3")
817        self._generic_definefont_parser(obj)
818        return obj
819
820    def _handle_tag_definebutton2(self):
821        """Handle the DefineButton2 tag."""
822        obj = _make_object("DefineButton2")
823        obj.ButtonId = unpack_ui16(self._src)
824
825        bc = BitConsumer(self._src)
826        bc.ReservedFlags = bc.u_get(7)
827        bc.TrackAsMenu = bc.u_get(1)
828
829        obj.ActionOffset = unpack_ui16(self._src)
830
831        # characters
832        obj.Characters = characters = []
833        while True:
834            end_flag = unpack_ui8(self._src)
835            if end_flag == 0:
836                # all done
837                obj.CharacterEndFlag = 0
838                break
839
840            # we have a BUTTONRECORD, let's go back the 8 bits and set the obj
841            self._src.seek(-1, io.SEEK_CUR)
842            character = _make_object("ButtonRecord")
843            characters.append(character)
844
845            bc = BitConsumer(self._src)
846            character.ButtonReserved = bc.u_get(2)
847            character.ButtonHasBlendMode = bc.u_get(1)
848            character.ButtonHasFilterList = bc.u_get(1)
849            character.ButtonStateHitTest = bc.u_get(1)
850            character.ButtonStateDown = bc.u_get(1)
851            character.ButtonStateOver = bc.u_get(1)
852            character.ButtonStateUp = bc.u_get(1)
853
854            character.CharacterId = unpack_ui16(self._src)
855            character.PlaceDepth = unpack_ui16(self._src)
856            character.PlaceMatrix = self._get_struct_matrix()
857            character.ColorTransform = self._get_struct_cxformwithalpha()
858            if character.ButtonHasFilterList:
859                character.FilterList = self._get_struct_filterlist()
860            if character.ButtonHasBlendMode:
861                character.BlendMode = unpack_ui8(self._src)
862
863        obj.Actions = actions = []
864        still_have_actions = True
865        while still_have_actions:
866            end_flag = unpack_ui16(self._src)
867            if end_flag == 0:
868                # this is the last action, parse it and then exit
869                still_have_actions = False
870
871            bca = _make_object("ButtonCondAction")
872            actions.append(bca)
873            bca.CondActionSize = end_flag
874
875            bc = BitConsumer(self._src)
876            bca.CondIdleToOverDown = bc.u_get(1)
877            bca.CondOutDownToIdle = bc.u_get(1)
878            bca.CondOutDownToOverDown = bc.u_get(1)
879            bca.CondOverDownToOutDown = bc.u_get(1)
880            bca.CondOverDownToOverUp = bc.u_get(1)
881            bca.CondOverUpToOverDown = bc.u_get(1)
882            bca.CondOverUpToIdle = bc.u_get(1)
883            bca.CondIdleToOverUp = bc.u_get(1)
884
885            bca.CondKeyPress = bc.u_get(7)
886            bca.CondOverDownToIdle = bc.u_get(1)
887            bca.Actions = self._generic_action_parser()
888
889        return obj
890
891    def _handle_tag_enabledebugger2(self):
892        """Handle the EnableDebugger2 tag."""
893        obj = _make_object("EnableDebugger2")
894        obj.Reserved = unpack_ui16(self._src)
895        obj.Password = self._get_struct_string()
896        return obj
897
898    def _handle_tag_scriptlimits(self):
899        """Handle the ScriptLimits tag."""
900        obj = _make_object("ScriptLimits")
901        obj.MaxRecursionDepth = unpack_ui16(self._src)
902        obj.ScriptTimeoutSeconds = unpack_ui16(self._src)
903        return obj
904
905    def _handle_tag_framelabel(self):
906        """Handle the FrameLabel tag."""
907        obj = _make_object("FrameLabel")
908        obj.Name = self._get_struct_string()
909        return obj
910
911    def _handle_tag_jpegtables(self):
912        """Handle the JPEGTables tag."""
913        obj = _make_object("JPEGTables")
914        assert self._src.read(2) == b'\xFF\xD8'  # SOI marker
915        eoimark1 = eoimark2 = None
916        allbytes = [b'\xFF\xD8']
917        while not (eoimark1 == b'\xFF' and eoimark2 == b'\xD9'):
918            newbyte = self._src.read(1)
919            allbytes.append(newbyte)
920            eoimark1 = eoimark2
921            eoimark2 = newbyte
922
923        # concatenate everything, removing the end mark
924        obj.JPEGData = b"".join(allbytes[:-2])
925        return obj
926
927    def _handle_tag_definefontalignzones(self):
928        """Handle the DefineFontAlignZones tag."""
929        obj = _make_object("DefineFontAlignZones")
930        obj.FontId = unpack_ui16(self._src)
931        bc = BitConsumer(self._src)
932        obj.CSMTableHint = bc.u_get(2)
933        obj.Reserved = bc.u_get(6)
934
935        obj.ZoneTable = zone_records = []
936        glyph_count = self._last_defined_glyphs_quantity
937        self._last_defined_glyphs_quantity = None
938        for _ in range(glyph_count):
939            zone_record = _make_object("ZoneRecord")
940            zone_records.append(zone_record)
941            zone_record.NumZoneData = unpack_ui8(self._src)
942            zone_record.ZoneData = zone_data = []
943            for _ in range(zone_record.NumZoneData):
944                zone_datum = _make_object("ZoneData")
945                zone_data.append(zone_datum)
946                zone_datum.AlignmentCoordinate = unpack_float16(self._src)
947                zone_datum.Range = unpack_float16(self._src)
948            bc = BitConsumer(self._src)
949            zone_record.Reserved = bc.u_get(6)
950            zone_record.ZoneMaskY = bc.u_get(1)
951            zone_record.ZoneMaskX = bc.u_get(1)
952        return obj
953
954    def _handle_tag_definefontname(self):
955        """Handle the DefineFontName tag."""
956        obj = _make_object("DefineFontName")
957        obj.FontId = unpack_ui16(self._src)
958        obj.FontName = self._get_struct_string()
959        obj.FontCopyright = self._get_struct_string()
960        return obj
961
962    def _handle_tag_csmtextsettings(self):
963        """Handle the CSMTextSettings tag."""
964        obj = _make_object("CSMTextSettings")
965        obj.TextId = unpack_ui16(self._src)
966        bc = BitConsumer(self._src)
967        obj.UseFlashType = bc.u_get(2)
968        obj.GridFit = bc.u_get(3)
969        obj.Reserved1 = bc.u_get(3)
970        obj.Thickness = unpack_float(self._src)
971        obj.Sharpness = unpack_float(self._src)
972        obj.Reserved2 = unpack_ui8(self._src)
973        return obj
974
975    def _get_struct_rect(self):
976        """Get the RECT structure."""
977        bc = BitConsumer(self._src)
978        nbits = bc.u_get(5)
979        return tuple(bc.u_get(nbits) for _ in range(4))
980
981    def _get_struct_rgb(self):
982        """Get the RGB structure."""
983        return [unpack_ui8(self._src) for _ in range(3)]
984
985    def _get_struct_rgba(self):
986        """Get the RGBA structure."""
987        return [unpack_ui8(self._src) for _ in range(4)]
988
989    def _get_struct_langcode(self):
990        """Get the LANGCODE structure."""
991        code = unpack_ui8(self._src)
992        return LANGCODES[code]
993
994    def _get_struct_kerningrecord(self, font_flags_wide_codes):
995        """Get the KERNINGRECORD structure."""
996        getter = unpack_ui16 if font_flags_wide_codes else unpack_ui8
997        data = {}
998        data['FontKerningCode1'] = getter(self._src)
999        data['FontKerningCode2'] = getter(self._src)
1000        data['FontKerningAdjustment'] = unpack_si16(self._src)
1001        return data
1002
1003    def _get_struct_clipactions(self):
1004        """Get the several CLIPACTIONRECORDs."""
1005        obj = _make_object("ClipActions")
1006
1007        # In SWF 5 and earlier, these are 2 bytes wide; in SWF 6
1008        # and later 4 bytes
1009        clipeventflags_size = 2 if self._version <= 5 else 4
1010        clipactionend_size = 2 if self._version <= 5 else 4
1011        all_zero = b"\x00" * clipactionend_size
1012
1013        assert unpack_ui16(self._src) == 0  # reserved
1014        obj.AllEventFlags = self._src.read(clipeventflags_size)
1015
1016        obj.ClipActionRecords = records = []
1017        while True:
1018            next_bytes = self._src.read(clipactionend_size)
1019            if next_bytes == all_zero:
1020                # was the ClipActionEndFlag
1021                return
1022
1023            record = _make_object("ClipActionRecord")
1024            records.append(record)
1025
1026            # as event flags and end flag has same size, we can do this trick
1027            record.EventFlags = next_bytes
1028            record.ActionRecordSize = unpack_ui32(self._src)
1029            record.TheRestTODO = self._src.read(record.ActionRecordSize)
1030
1031            # FIXME: this struct needs more work; the EventFlags should be
1032            # expanded and each ActionRecord(s) should be detailed more
1033        return obj
1034
1035    def _get_struct_string(self):
1036        """Get the STRING structure."""
1037        data = []
1038        while True:
1039            t = self._src.read(1)
1040            if t == b'\x00':
1041                break
1042            data.append(t)
1043        val = b''.join(data)
1044        return val.decode("utf8")
1045
1046    def _get_struct_matrix(self):
1047        """Get the values for the MATRIX record."""
1048        obj = _make_object("Matrix")
1049        bc = BitConsumer(self._src)
1050
1051        # scale
1052        obj.HasScale = bc.u_get(1)
1053        if obj.HasScale:
1054            obj.NScaleBits = n_scale_bits = bc.u_get(5)
1055            obj.ScaleX = bc.u_get(n_scale_bits)
1056            obj.ScaleY = bc.u_get(n_scale_bits)
1057
1058        # rotate
1059        obj.HasRotate = bc.u_get(1)
1060        if obj.HasRotate:
1061            obj.NRotateBits = n_rotate_bits = bc.u_get(5)
1062            obj.RotateSkew0 = bc.u_get(n_rotate_bits)
1063            obj.RotateSkew1 = bc.u_get(n_rotate_bits)
1064
1065        # translate
1066        obj.NTranslateBits = n_translate_bits = bc.u_get(5)
1067        obj.TranslateX = bc.u_get(n_translate_bits)
1068        obj.TranslateY = bc.u_get(n_translate_bits)
1069        return obj
1070
1071    def _get_struct_cxformwithalpha(self):
1072        """Get the values for the CXFORMWITHALPHA record."""
1073        obj = _make_object("CXformWithAlpha")
1074        bc = BitConsumer(self._src)
1075
1076        obj.HasAddTerms = bc.u_get(1)
1077        obj.HasMultTerms = bc.u_get(1)
1078        obj.NBits = nbits = bc.u_get(4)
1079
1080        if obj.HasMultTerms:
1081            obj.RedMultTerm = bc.u_get(nbits)
1082            obj.GreenMultTerm = bc.u_get(nbits)
1083            obj.BlueMultTerm = bc.u_get(nbits)
1084            obj.AlphaMultTerm = bc.u_get(nbits)
1085
1086        if obj.HasAddTerms:
1087            obj.RedAddTerm = bc.u_get(nbits)
1088            obj.GreenAddTerm = bc.u_get(nbits)
1089            obj.BlueAddTerm = bc.u_get(nbits)
1090            obj.AlphaAddTerm = bc.u_get(nbits)
1091
1092        return obj
1093
1094    def _get_shaperecords(self, num_fill_bits,
1095                          num_line_bits, shape_number):
1096        """Return an array of SHAPERECORDS."""
1097        shape_records = []
1098        bc = BitConsumer(self._src)
1099
1100        while True:
1101            type_flag = bc.u_get(1)
1102            if type_flag:
1103                # edge record
1104                straight_flag = bc.u_get(1)
1105                num_bits = bc.u_get(4)
1106                if straight_flag:
1107                    record = _make_object('StraightEdgeRecord')
1108                    record.TypeFlag = 1
1109                    record.StraightFlag = 1
1110                    record.NumBits = num_bits
1111                    record.GeneralLineFlag = general_line_flag = bc.u_get(1)
1112                    if general_line_flag:
1113                        record.DeltaX = bc.s_get(num_bits + 2)
1114                        record.DeltaY = bc.s_get(num_bits + 2)
1115                    else:
1116                        record.VertLineFlag = vert_line_flag = bc.s_get(1)
1117                        if vert_line_flag:
1118                            record.DeltaY = bc.s_get(num_bits + 2)
1119                        else:
1120                            record.DeltaX = bc.s_get(num_bits + 2)
1121                else:
1122                    record = _make_object('CurvedEdgeRecord')
1123                    record.TypeFlag = 1
1124                    record.StraightFlag = 0
1125                    record.NumBits = num_bits
1126                    record.ControlDeltaX = bc.s_get(num_bits + 2)
1127                    record.ControlDeltaY = bc.s_get(num_bits + 2)
1128                    record.AnchorDeltaX = bc.s_get(num_bits + 2)
1129                    record.AnchorDeltaY = bc.s_get(num_bits + 2)
1130
1131            else:
1132                # non edge record
1133                record = _make_object('StyleChangeRecord')
1134                record.TypeFlag = 0
1135
1136                five_bits = [bc.u_get(1) for _ in range(5)]
1137                if not any(five_bits):
1138                    # the five bits are zero, this is an EndShapeRecord
1139                    break
1140
1141                # we're not done, store the proper flags
1142                (record.StateNewStyles, record.StateLineStyle,
1143                    record.StateFillStyle1, record.StateFillStyle0,
1144                    record.StateMoveTo) = five_bits
1145
1146                if record.StateMoveTo:
1147                    record.MoveBits = move_bits = bc.u_get(5)
1148                    record.MoveDeltaX = bc.s_get(move_bits)
1149                    record.MoveDeltaY = bc.s_get(move_bits)
1150                if record.StateFillStyle0:
1151                    record.FillStyle0 = bc.u_get(num_fill_bits)
1152                if record.StateFillStyle1:
1153                    record.FillStyle1 = bc.u_get(num_fill_bits)
1154                if record.StateLineStyle:
1155                    record.LineStyle = bc.u_get(num_line_bits)
1156
1157                if record.StateNewStyles:
1158                    record.FillStyles = self._get_struct_fillstylearray(
1159                        shape_number)
1160                    record.LineStyles = self._get_struct_linestylearray(
1161                        shape_number)
1162                    # these two not only belong to the record, but also
1163                    # modifies the number of bits read in the future
1164                    # if shape number bigs enough (didn't find this in the
1165                    # spec, but works for now, maybe '2' is not the limit...)
1166                    if shape_number > 2:
1167                        record.NumFillBits = num_fill_bits = bc.u_get(4)
1168                        record.NumLineBits = num_line_bits = bc.u_get(4)
1169                    else:
1170                        record.NumFillBits = bc.u_get(4)
1171                        record.NumLineBits = bc.u_get(4)
1172
1173                    # reset the BC here, as the structures just read work at
1174                    # byte level
1175                    bc = BitConsumer(self._src)
1176
1177            shape_records.append(record)
1178        return shape_records
1179
1180    def _get_struct_shape(self):
1181        """Get the values for the SHAPE record."""
1182        obj = _make_object("Shape")
1183        bc = BitConsumer(self._src)
1184        obj.NumFillBits = n_fill_bits = bc.u_get(4)
1185        obj.NumLineBits = n_line_bits = bc.u_get(4)
1186        obj.ShapeRecords = self._get_shaperecords(
1187            n_fill_bits, n_line_bits, 0)
1188        return obj
1189
1190    def _get_struct_fillstyle(self, shape_number):
1191        """Get the values for the FILLSTYLE record."""
1192        obj = _make_object("FillStyle")
1193        obj.FillStyleType = style_type = unpack_ui8(self._src)
1194
1195        if style_type == 0x00:
1196            if shape_number <= 2:
1197                obj.Color = self._get_struct_rgb()
1198            else:
1199                obj.Color = self._get_struct_rgba()
1200
1201        if style_type in (0x10, 0x12, 0x13):
1202            obj.GradientMatrix = self._get_struct_matrix()
1203
1204        if style_type in (0x10, 0x12):
1205            obj.Gradient = self._get_struct_gradient(shape_number)
1206        if style_type == 0x13:
1207            obj.Gradient = self._get_struct_focalgradient(shape_number)
1208
1209        if style_type in (0x40, 0x41, 0x42, 0x43):
1210            obj.BitmapId = unpack_ui16(self._src)
1211            obj.BitmapMatrix = self._get_struct_matrix()
1212        return obj
1213
1214    def _get_struct_fillstylearray(self, shape_number):
1215        """Get the values for the FILLSTYLEARRAY record."""
1216        obj = _make_object("FillStyleArray")
1217        obj.FillStyleCount = count = unpack_ui8(self._src)
1218        if count == 0xFF:
1219            obj.FillStyleCountExtended = count = unpack_ui16(self._src)
1220        obj.FillStyles = [self._get_struct_fillstyle(shape_number)
1221                          for _ in range(count)]
1222        return obj
1223
1224    def _get_struct_linestylearray(self, shape_number):
1225        """Get the values for the LINESTYLEARRAY record."""
1226        obj = _make_object("LineStyleArray")
1227        obj.LineStyleCount = count = unpack_ui8(self._src)
1228        if count == 0xFF:
1229            obj.LineStyleCountExtended = count = unpack_ui16(self._src)
1230        obj.LineStyles = line_styles = []
1231
1232        for _ in range(count):
1233            if shape_number <= 3:
1234                record = _make_object("LineStyle")
1235                record.Width = unpack_ui16(self._src)
1236                if shape_number <= 2:
1237                    record.Color = self._get_struct_rgb()
1238                else:
1239                    record.Color = self._get_struct_rgba()
1240            else:
1241                record = _make_object("LineStyle2")
1242                record.Width = unpack_ui16(self._src)
1243
1244                bc = BitConsumer(self._src)
1245                record.StartCapStyle = bc.u_get(2)
1246                record.JoinStyle = bc.u_get(2)
1247                record.HasFillFlag = bc.u_get(1)
1248                record.NoHScaleFlag = bc.u_get(1)
1249                record.NoVScaleFlag = bc.u_get(1)
1250                record.PixelHintingFlag = bc.u_get(1)
1251
1252                bc.u_get(5)  # reserved
1253                record.NoClose = bc.u_get(1)
1254                record.EndCapStyle = bc.u_get(2)
1255
1256                if record.JoinStyle == 2:
1257                    record.MiterLimitFactor = unpack_ui16(self._src)
1258                if record.HasFillFlag == 0:
1259                    record.Color = self._get_struct_rgba()
1260                else:
1261                    record.Color = self._get_struct_fillstyle(shape_number)
1262
1263            line_styles.append(record)
1264
1265        return obj
1266
1267    def _get_struct_encodedu32(self):
1268        """Get a EncodedU32 number."""
1269        useful = []
1270        while True:
1271            byte = ord(self._src.read(1))
1272            useful.append(byte)
1273            if byte < 127:
1274                # got all the useful bytes
1275                break
1276
1277        # transform into bits reordering the bytes
1278        useful = ['00000000' + bin(b)[2:] for b in useful[::-1]]
1279
1280        # get the top 7 (*seven*, as the eight one is the flag) and convert
1281        return int(''.join([b[-7:] for b in useful]), 2)
1282
1283    def _get_struct_shapewithstyle(self, shape_number):
1284        """Get the values for the SHAPEWITHSTYLE record."""
1285        obj = _make_object("ShapeWithStyle")
1286        obj.FillStyles = self._get_struct_fillstylearray(shape_number)
1287        obj.LineStyles = self._get_struct_linestylearray(shape_number)
1288        bc = BitConsumer(self._src)
1289        obj.NumFillBits = n_fill_bits = bc.u_get(4)
1290        obj.NumlineBits = n_line_bits = bc.u_get(4)
1291        obj.ShapeRecords = self._get_shaperecords(
1292            n_fill_bits, n_line_bits, shape_number)
1293        return obj
1294
1295    def _get_struct_gradient(self, shape_number):
1296        """Get the values for the GRADIENT record."""
1297        obj = _make_object("Gradient")
1298        bc = BitConsumer(self._src)
1299        obj.SpreadMode = bc.u_get(2)
1300        obj.InterpolationMode = bc.u_get(2)
1301        obj.NumGradients = bc.u_get(4)
1302        obj.GradientRecords = gradient_records = []
1303
1304        for _ in range(obj.NumGradients):
1305            record = _make_object("GradRecord")
1306            gradient_records.append(record)
1307            record.Ratio = unpack_ui8(self._src)
1308            if shape_number <= 2:
1309                record.Color = self._get_struct_rgb()
1310            else:
1311                record.Color = self._get_struct_rgba()
1312        return obj
1313
1314    def _get_struct_focalgradient(self, shape_number):
1315        """Get the values for the FOCALGRADIENT record."""
1316        obj = _make_object("FocalGradient")
1317        bc = BitConsumer(self._src)
1318        obj.SpreadMode = bc.u_get(2)
1319        obj.InterpolationMode = bc.u_get(2)
1320        obj.NumGradients = bc.u_get(4)
1321        obj.GradientRecords = gradient_records = []
1322
1323        for _ in range(obj.NumGradients):
1324            record = _make_object("GradRecord")
1325            gradient_records.append(record)
1326            record.Ratio = unpack_ui8(self._src)
1327            if shape_number <= 2:
1328                record.Color = self._get_struct_rgb()
1329            else:
1330                record.Color = self._get_struct_rgba()
1331
1332        obj.FocalPoint = unpack_fixed8(self._src)
1333        return obj
1334
1335    def _get_struct_filterlist(self):
1336        """Get the values for the FILTERLIST record."""
1337        obj = _make_object("FilterList")
1338        obj.NumberOfFilters = unpack_ui8(self._src)
1339        obj.Filter = filters = []
1340        # how to decode each filter type (and name), according to the filter id
1341        filter_type = [
1342            ("DropShadowFilter", self._get_struct_dropshadowfilter),  # 0
1343            ("BlurFilter", self._get_struct_blurfilter),  # 1
1344            ("GlowFilter", self._get_struct_glowfilter),  # 2...
1345            ("BevelFilter", self._get_struct_bevelfilter),
1346            ("GradientGlowFilter", self._get_struct_gradientglowfilter),
1347            ("ConvolutionFilter", self._get_struct_convolutionfilter),
1348            ("ColorMatrixFilter", self._get_struct_colormatrixfilter),
1349            ("GradientBevelFilter", self._get_struct_gradientbevelfilter),  # 7
1350        ]
1351
1352        for _ in range(obj.NumberOfFilters):
1353            _filter = _make_object("Filter")
1354            filters.append(_filter)
1355
1356            _filter.FilterId = unpack_ui8(self._src)
1357            name, func = filter_type[_filter.FilterId]
1358            setattr(_filter, name, func())
1359
1360    def _get_struct_dropshadowfilter(self):
1361        """Get the values for the DROPSHADOWFILTER record."""
1362        obj = _make_object("DropShadowFilter")
1363        obj.DropShadowColor = self._get_struct_rgba()
1364        obj.BlurX = unpack_fixed16(self._src)
1365        obj.BlurY = unpack_fixed16(self._src)
1366        obj.Angle = unpack_fixed16(self._src)
1367        obj.Distance = unpack_fixed16(self._src)
1368        obj.Strength = unpack_fixed8(self._src)
1369        bc = BitConsumer(self._src)
1370        obj.InnerShadow = bc.u_get(1)
1371        obj.Knockout = bc.u_get(1)
1372        obj.CompositeSource = bc.u_get(1)
1373        obj.Passes = bc.u_get(5)
1374        return obj
1375
1376    def _get_struct_blurfilter(self):
1377        """Get the values for the BLURFILTER record."""
1378        obj = _make_object("BlurFilter")
1379        obj.BlurX = unpack_fixed16(self._src)
1380        obj.BlurY = unpack_fixed16(self._src)
1381        bc = BitConsumer(self._src)
1382        obj.Passes = bc.u_get(5)
1383        obj.Reserved = bc.u_get(3)
1384        return obj
1385
1386    def _get_struct_glowfilter(self):
1387        """Get the values for the GLOWFILTER record."""
1388        obj = _make_object("GlowFilter")
1389        obj.GlowColor = self._get_struct_rgba()
1390        obj.BlurX = unpack_fixed16(self._src)
1391        obj.BlurY = unpack_fixed16(self._src)
1392        obj.Strength = unpack_fixed8(self._src)
1393        bc = BitConsumer(self._src)
1394        obj.InnerGlow = bc.u_get(1)
1395        obj.Knockout = bc.u_get(1)
1396        obj.CompositeSource = bc.u_get(1)
1397        obj.Passes = bc.u_get(5)
1398        return obj
1399
1400    def _get_struct_bevelfilter(self):
1401        """Get the values for the BEVELFILTER record."""
1402        obj = _make_object("BevelFilter")
1403        obj.ShadowColor = self._get_struct_rgba()
1404        obj.HighlightColor = self._get_struct_rgba()
1405        obj.BlurX = unpack_fixed16(self._src)
1406        obj.BlurY = unpack_fixed16(self._src)
1407        obj.Angle = unpack_fixed16(self._src)
1408        obj.Distance = unpack_fixed16(self._src)
1409        obj.Strength = unpack_fixed8(self._src)
1410        bc = BitConsumer(self._src)
1411        obj.InnerShadow = bc.u_get(1)
1412        obj.Knockout = bc.u_get(1)
1413        obj.CompositeSource = bc.u_get(1)
1414        obj.OnTop = bc.u_get(1)
1415        obj.Passes = bc.u_get(4)
1416        return obj
1417
1418    def _get_struct_gradientglowfilter(self):
1419        """Get the values for the GRADIENTGLOWFILTER record."""
1420        obj = _make_object("GradientBevelFilter")
1421        obj.NumColors = num_colors = unpack_ui8(self._src)
1422        obj.GradientColors = [self._get_struct_rgba()
1423                              for _ in range(num_colors)]
1424        obj.GradientRatio = [unpack_ui8(self._src)
1425                             for _ in range(num_colors)]
1426        obj.BlurX = unpack_fixed16(self._src)
1427        obj.BlurY = unpack_fixed16(self._src)
1428        obj.Angle = unpack_fixed16(self._src)
1429        obj.Distance = unpack_fixed16(self._src)
1430        obj.Strength = unpack_fixed8(self._src)
1431        bc = BitConsumer(self._src)
1432        obj.InnerShadow = bc.u_get(1)
1433        obj.Knockout = bc.u_get(1)
1434        obj.CompositeSource = bc.u_get(1)
1435        obj.OnTop = bc.u_get(1)
1436        obj.Passes = bc.u_get(4)
1437        return obj
1438
1439    def _get_struct_convolutionfilter(self):
1440        """Get the values for the CONVOLUTIONFILTER record."""
1441        obj = _make_object("ConvolutionFilter")
1442        obj.MatrixX = unpack_ui8(self._src)
1443        obj.MatrixY = unpack_ui8(self._src)
1444        obj.Divisor = unpack_float(self._src)
1445        obj.Bias = unpack_float(self._src)
1446
1447        _quant = obj.MatrixX * obj.MatrixY
1448        obj.Matrix = [unpack_float(self._src) for _ in range(_quant)]
1449
1450        obj.DefaultColor = self._get_struct_rgba()
1451        bc = BitConsumer(self._src)
1452        obj.Reserved = bc.u_get(6)
1453        obj.Clamp = bc.u_get(1)
1454        obj.PreserveAlpha = bc.u_get(1)
1455        return obj
1456
1457    def _get_struct_colormatrixfilter(self):
1458        """Get the values for the COLORMATRIXFILTER record."""
1459        obj = _make_object("ColorMatrixFilter")
1460        obj.Matrix = [unpack_float(self._src) for _ in range(20)]
1461        return obj
1462
1463    def _get_struct_gradientbevelfilter(self):
1464        """Get the values for the GRADIENTBEVELFILTER record."""
1465        obj = _make_object("GradientBevelFilter")
1466        obj.NumColors = num_colors = unpack_ui8(self._src)
1467        obj.GradientColors = [self._get_struct_rgba()
1468                              for _ in range(num_colors)]
1469        obj.GradientRatio = [unpack_ui8(self._src)
1470                             for _ in range(num_colors)]
1471        obj.BlurX = unpack_fixed16(self._src)
1472        obj.BlurY = unpack_fixed16(self._src)
1473        obj.Angle = unpack_fixed16(self._src)
1474        obj.Distance = unpack_fixed16(self._src)
1475        obj.Strength = unpack_fixed8(self._src)
1476        bc = BitConsumer(self._src)
1477        obj.InnerShadow = bc.u_get(1)
1478        obj.Knockout = bc.u_get(1)
1479        obj.CompositeSource = bc.u_get(1)
1480        obj.OnTop = bc.u_get(1)
1481        obj.Passes = bc.u_get(4)
1482        return obj
1483
1484    def _handle_actionconstantpool(self, _):
1485        """Handle the ActionConstantPool action."""
1486        obj = _make_object("ActionConstantPool")
1487        obj.Count = count = unpack_ui16(self._src)
1488        obj.ConstantPool = pool = []
1489        for _ in range(count):
1490            pool.append(self._get_struct_string())
1491        yield obj
1492
1493    def _handle_actiongeturl(self, _):
1494        """Handle the ActionGetURL action."""
1495        obj = _make_object("ActionGetURL")
1496        obj.UrlString = self._get_struct_string()
1497        obj.TargetString = self._get_struct_string()
1498        yield obj
1499
1500    def _handle_actionpush(self, length):
1501        """Handle the ActionPush action."""
1502        init_pos = self._src.tell()
1503        while self._src.tell() < init_pos + length:
1504            obj = _make_object("ActionPush")
1505            obj.Type = unpack_ui8(self._src)
1506            # name and how to read each type
1507            push_types = {
1508                0: ("String", self._get_struct_string),
1509                1: ("Float", lambda: unpack_float(self._src)),
1510                2: ("Null", lambda: None),
1511                4: ("RegisterNumber", lambda: unpack_ui8(self._src)),
1512                5: ("Boolean", lambda: unpack_ui8(self._src)),
1513                6: ("Double", lambda: unpack_double(self._src)),
1514                7: ("Integer", lambda: unpack_ui32(self._src)),
1515                8: ("Constant8", lambda: unpack_ui8(self._src)),
1516                9: ("Constant16", lambda: unpack_ui16(self._src)),
1517            }
1518            name, func = push_types[obj.Type]
1519            setattr(obj, name, func())
1520            yield obj
1521
1522    def _handle_actiondefinefunction(self, _):
1523        """Handle the ActionDefineFunction action."""
1524        obj = _make_object("ActionDefineFunction")
1525        obj.FunctionName = self._get_struct_string()
1526        obj.NumParams = unpack_ui16(self._src)
1527        for i in range(1, obj.NumParams + 1):
1528            setattr(obj, "param" + str(i), self._get_struct_string())
1529        obj.CodeSize = unpack_ui16(self._src)
1530        yield obj
1531
1532    def _handle_actionif(self, _):
1533        """Handle the ActionIf action."""
1534        obj = _make_object("ActionIf")
1535        obj.BranchOffset = unpack_si16(self._src)
1536        yield obj
1537
1538    def _handle_actiondefinefunction2(self, _):
1539        """Handle the ActionDefineFunction2 action."""
1540        obj = _make_object("ActionDefineFunction2")
1541        obj.FunctionName = self._get_struct_string()
1542        obj.NumParams = unpack_ui16(self._src)
1543        obj.RegisterCount = unpack_ui8(self._src)
1544        bc = BitConsumer(self._src)
1545        obj.PreloadParentFlag = bc.u_get(1)
1546        obj.PreloadRootFlag = bc.u_get(1)
1547        obj.SupressSuperFlag = bc.u_get(1)
1548        obj.PreloadSuperFlag = bc.u_get(1)
1549        obj.SupressArgumentsFlag = bc.u_get(1)
1550        obj.PreloadArgumentsFlag = bc.u_get(1)
1551        obj.SupressThisFlag = bc.u_get(1)
1552        obj.PreloadThisFlag = bc.u_get(1)
1553        obj.Reserved = bc.u_get(7)
1554        obj.PreloadGlobalFlag = bc.u_get(1)
1555        obj.Parameters = parameters = []
1556        for _ in range(obj.NumParams):
1557            parameter = _make_object("Parameter")
1558            parameters.append(parameter)
1559            parameter.Register = unpack_ui8(self._src)
1560            parameter.ParamName = self._get_struct_string()
1561        obj.CodeSize = unpack_ui16(self._src)
1562        yield obj
1563
1564    def coverage(self):
1565        """Calculate the coverage of a file."""
1566        items_unk = collections.Counter()
1567        items_ok = collections.Counter()
1568
1569        def _go_deep(obj):
1570            """Recursive function to find internal attributes."""
1571            if type(obj).__name__ in ('UnknownObject', 'UnknownAction'):
1572                # blatantly unknown
1573                items_unk[obj.name] += 1
1574            elif obj.name in ('DefineMorphShape2', 'ClipActions'):
1575                # these are incomplete, see FIXMEs in the code above
1576                items_unk[obj.name] += 1
1577            else:
1578                # fully parsed
1579                items_ok[obj.name] += 1
1580
1581            for name in obj._attribs:
1582                attr = getattr(obj, name)
1583                if isinstance(attr, SWFObject):
1584                    _go_deep(attr)
1585
1586        for tag in self.tags:
1587            _go_deep(tag)
1588
1589        full_count = sum(items_ok.values()) + sum(items_unk.values())
1590        coverage = 100 * sum(items_ok.values()) / full_count
1591        print("Coverage is {:.1f}% of {} total items".format(coverage,
1592                                                             full_count))
1593        print("Most common parsed objects:")
1594        for k, v in items_ok.most_common(3):
1595            print("{:5d} {}".format(v, k))
1596        if items_unk:
1597            print("Most common Unknown objects")
1598            for k, v in items_unk.most_common(3):
1599                print("{:5d} {}".format(v, k))
1600
1601
1602def parsefile(filename):
1603    """Parse a SWF.
1604
1605    If you have a file object already, just use SWFParser directly.
1606    """
1607    with open(filename, 'rb') as fh:
1608        return SWFParser(fh)
1609