# Copyright 2013-2014 Facundo Batista # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . # # For further info, check http://github.com/facundobatista/yaswfp """Parse a SWF file and expose all its internals. This follows the SWF FILE FORMAT SPECIFICATION VERSION 19 which is not included in this project for your easier finding because Adobe forbids the spec distribution. The attributes names are CamelCase to match as close as possible the spec. Note: not all the spec is covered (work in progress!), there's a flag in the SWFParser to change the behaviour when an still-not-done object is found. """ import collections import io import os import sys import warnings import zlib sys.path.append(os.path.dirname(__file__)) sys.path.append(os.path.dirname(__file__) + '/../../') from helpers import ( BitConsumer, ReadQuantityController, unpack_si16, unpack_ui16, unpack_ui32, unpack_ui8, unpack_fixed8, unpack_fixed16, unpack_float16, unpack_float, unpack_double, ) VERSION = "0.9.3" # name of each tag (as a dict, not a list, for easier human consumption) TAG_NAMES = { 0: "End", 1: "ShowFrame", 2: "DefineShape", 4: "PlaceObject", 5: "RemoveObject", 6: "DefineBits", 7: "DefineButton", 8: "JPEGTables", 9: "SetBackgroundColor", 10: "DefineFont", 11: "DefineText", 12: "DoAction", 13: "DefineFontInfo", 14: "DefineSound", 15: "StartSound", 17: "DefineButtonSound", 18: "SoundStreamHead", 19: "SoundStreamBlock", 20: "DefineBitsLossless", 21: "DefineBitsJPEG2", 22: "DefineShape2", 23: "DefineButtonCxform", 24: "Protect", 26: "PlaceObject2", 28: "RemoveObject2", 32: "DefineShape3", 33: "DefineText2", 34: "DefineButton2", 35: "DefineBitsJPEG3", 36: "DefineBitsLossless2", 37: "DefineEditText", 39: "DefineSprite", 43: "FrameLabel", 45: "SoundStreamHead2", 46: "DefineMorphShape", 48: "DefineFont2", 56: "ExportAssets", 57: "ImportAssets", 58: "EnableDebugger", 59: "DoInitAction", 60: "DefineVideoStream", 61: "VideoFrame", 62: "DefineFontInfo2", 64: "EnableDebugger2", 65: "ScriptLimits", 66: "SetTabIndex", 69: "FileAttributes", 70: "PlaceObject3", 71: "ImportAssets2", 73: "DefineFontAlignZones", 74: "CSMTextSettings", 75: "DefineFont3", 76: "SymbolClass", 77: "Metadata", 78: "DefineScalingGrid", 82: "DoABC", 83: "DefineShape4", 84: "DefineMorphShape2", 86: "DefineSceneAndFrameLabelData", 87: "DefineBinaryData", 88: "DefineFontName", 89: "StartSound2", 90: "DefineBitsJPEG4", 91: "DefineFont4", } LANGCODES = { 0: "Sys", 1: "Latin", 2: "Japanese", 3: "Korean", 4: "Simplified Chinese", 5: "Traditional Chinese", } ACTION_NAMES = { 0x04: 'ActionNextFrame', 0x05: 'ActionPrevFrame', 0x06: 'ActionPlay', 0x07: 'ActionStop', 0x08: 'ActionToggleQualty', 0x09: 'ActionStopSounds', 0x0A: 'ActionAdd', 0x0B: 'ActionSubtract', 0x0C: 'ActionMultiply', 0x0D: 'ActionDivide', 0x0E: 'ActionEquals', 0x0F: 'ActionLess', 0x10: 'ActionAnd', 0x11: 'ActionOr', 0x12: 'ActionNot', 0x13: 'ActionStringEquals', 0x14: 'ActionStringLength', 0x15: 'ActionStringExtract', 0x17: 'ActionPop', 0x18: 'ActionToInteger', 0x1C: 'ActionGetVariable', 0x1D: 'ActionSetVariable', 0x20: 'ActionSetTarget2', 0x21: 'ActionStringAdd', 0x22: 'ActionGetProperty', 0x23: 'ActionSetProperty', 0x24: 'ActionCloneSprite', 0x25: 'ActionRemoveSprite', 0x26: 'ActionTrace', 0x27: 'ActionStartDrag', 0x28: 'ActionEndDrag', 0x29: 'ActionStringLess', 0x2A: 'ActionThrow', 0x2B: 'ActionCastOp', 0x2C: 'ActionImplementsOp', 0x30: 'ActionRandomNumber', 0x31: 'ActionMBStringLength', 0x32: 'ActionCharToAscii', 0x33: 'ActionAsciiToChar', 0x34: 'ActionGetTime', 0x35: 'ActionMBStringExtract', 0x36: 'ActionMBCharToAscii', 0x37: 'ActionMBAsciiToChar', 0x3A: 'ActionDelete', 0x3B: 'ActionDelete2', 0x3C: 'ActionDefineLocal', 0x3D: 'ActionCallFunction', 0x3E: 'ActionReturn', 0x3F: 'ActionModulo', 0x40: 'ActionNewObject', 0x41: 'ActionDefineLocal2', 0x42: 'ActionInitArray', 0x43: 'ActionInitObject', 0x44: 'ActionTypeOf', 0x45: 'ActionTargetPath', 0x46: 'ActionEnumerate', 0x47: 'ActionAdd2', 0x48: 'ActionLess2', 0x49: 'ActionEquals2', 0x4A: 'ActionToNumber', 0x4B: 'ActionToString', 0x4C: 'ActionPushDuplicate', 0x4D: 'ActionStackSwap', 0x4E: 'ActionGetMember', 0x4F: 'ActionSetMember', 0x50: 'ActionIncrement', 0x51: 'ActionDecrement', 0x52: 'ActionCallMethod', 0x53: 'ActionNewMethod', 0x54: 'ActionInstanceOf', 0x55: 'ActionEnumerate2', 0x60: 'ActionBitAnd', 0x61: 'ActionBitOr', 0x62: 'ActionBitXor', 0x63: 'ActionBitLShift', 0x64: 'ActionBitRShift', 0x65: 'ActionBitURShift', 0x66: 'ActionStrictEquals', 0x67: 'ActionGreater', 0x68: 'ActionStringGreater', 0x69: 'ActionExtends', 0x81: 'ActionGotoFrame', 0x83: 'ActionGetURL', 0x87: 'ActionStoreRegister', 0x88: 'ActionConstantPool', 0x8A: 'ActionWaitForFrame', 0x8B: 'ActionSetTarget', 0x8C: 'ActionGoToLabel', 0x8D: 'ActionWaitForFrame2', 0x8E: 'ActionDefineFunction2', 0x8F: 'ActionTry', 0x94: 'ActionWith', 0x96: 'ActionPush', 0x99: 'ActionJump', 0x9A: 'ActionGetURL2', 0x9B: 'ActionDefineFunction', 0x9D: 'ActionIf', 0x9E: 'ActionCall', 0x9F: 'ActionGotoFrame2', } def _str(obj): """Show nicely the generic object received.""" values = [] for name in obj._attribs: val = getattr(obj, name) if isinstance(val, str): val = repr(val) val = str(val) if len(str(val)) < 10 else "(...)" values.append((name, val)) values = ", ".join("{}={}".format(k, v) for k, v in values) return "{}({})".format(obj.__class__.__name__, values) def _repr(obj): """Show the received object as precise as possible.""" vals = ", ".join("{}={!r}".format( name, getattr(obj, name)) for name in obj._attribs) if vals: t = "{}(name={}, {})".format(obj.__class__.__name__, obj.name, vals) else: t = "{}(name={})".format(obj.__class__.__name__, obj.name) return t class SWFObject: """A super class for all the objects created here.""" def __init__(self): self._attribs = [] def __setattr__(self, name, value): if name != "_attribs": if name not in self._attribs: self._attribs.append(name) super(SWFObject, self).__setattr__(name, value) def _make_object(name): """Create a generic object for the tags.""" klass = type(name, (SWFObject,), {'__str__': _str, '__repr__': _repr, 'name': name}) return klass() class SWFParser: """Read (at a byte or bit level) the SWF structure from a fileobject. When the parser finds a structure that still can't process (because more programming is needed), will just return an UnknownObject object with the unparsed bytes, or will raise an exception if you set the unknown_alert flag:: SWFParser.unknown_alert = True """ unknown_alert = False def __init__(self, src, read_twips=True): self._src = src self._read_twips = read_twips self._version = None self._last_defined_glyphs_quantity = None self.header = self._get_header() self.tags = self._process_tags() def _get_header(self): """Parse the SWF header.""" fh = self._src obj = _make_object("Header") # first part of the header obj.Signature = sign = "".join(chr(unpack_ui8(fh)) for _ in range(3)) obj.Version = self._version = unpack_ui8(fh) obj.FileLength = file_length = unpack_ui32(fh) # deal with compressed content if sign[0] == 'C': uncompressed = zlib.decompress(fh.read()) if len(uncompressed) + 8 != file_length: raise ValueError("Problems dealing with compressed content") fh = self._src = io.BytesIO(uncompressed) # second part of the header obj.FrameSize = self._get_struct_rect() obj.FrameRate = unpack_ui16(fh) obj.FrameCount = unpack_ui16(fh) return obj def _process_tags(self): """Get a sequence of tags.""" tags = [] while True: tag_bf = unpack_ui16(self._src) tag_type = tag_bf >> 6 # upper 10 bits if tag_type == 0: # the end break tag_len = tag_bf & 0x3f # last 6 bits if tag_len == 0x3f: # the length is the next four bytes! tag_len = unpack_ui32(self._src) try: tag_name = TAG_NAMES[tag_type] except KeyError: warnings.warn('unkonwn tag type: {}'.format(tag_type)) # malformed SWF, create and unknown object with malformed tag tag_payload = self._src.read(tag_len) _dict = { '__str__': _repr, '__repr__': _repr, 'name': 'UnspecifiedObject(tag={!r})'.format(tag_type), } tag = type("UnknownObject", (SWFObject,), _dict)() tag.raw_payload = tag_payload tags.append(tag) continue try: tag_meth = getattr(self, "_handle_tag_" + tag_name.lower()) except AttributeError: if self.unknown_alert: raise ValueError("Unknown tag: " + repr(tag_name)) warnings.warn('tag not supported: {}'.format(tag_name)) tag_payload = self._src.read(tag_len) _dict = {'__str__': _repr, '__repr__': _repr, 'name': tag_name} tag = type("UnknownObject", (SWFObject,), _dict)() tag.raw_payload = tag_payload tags.append(tag) continue # we know the tag type, and have the handler, let's process it prev_pos = self._src.tell() self._src.guard = tag_len try: with ReadQuantityController(self._src, tag_len): tag = tag_meth() assert tag is not None, tag_name except ValueError as e: warnings.warn('processing {} tag: {}'.format(tag_name, e)) # an attempt to read too much happened; create a failing # object with the raw payload self._src.guard = None self._src.seek(prev_pos) tag_payload = self._src.read(tag_len) _dict = {'__str__': _repr, '__repr__': _repr, 'name': tag_name} tag = type("FailingObject", (SWFObject,), _dict)() tag.raw_payload = tag_payload tags.append(tag) return tags def _handle_tag_definebits(self): """Handle the DefineBits tag.""" tag_end = self._src.tell() + self._src.guard obj = _make_object("DefineBits") obj.CharacterID = unpack_ui16(self._src) obj.JPEGData = self._get_raw_bytes(-tag_end) return obj def _handle_tag_definebitsjpeg2(self): """Handle the DefineBitsJPEG2 tag.""" tag_end = self._src.tell() + self._src.guard obj = _make_object("DefineBitsJPEG2") obj.CharacterID = unpack_ui16(self._src) obj.ImageData = self._get_raw_bytes(-tag_end) return obj def _generic_definebitsjpeg_parser(self, obj, version): """Handle the DefineBitsJPEGN tag.""" tag_end = self._src.tell() + self._src.guard obj.CharacterID = unpack_ui16(self._src) obj.AlphaDataOffset = unpack_ui32(self._src) if 4 == version: # FIXME: 8.8 fixed point format in Comment obj.DeblockParam = unpack_ui16(self._src) obj.ImageData = self._get_raw_bytes(obj.AlphaDataOffset) obj.BitmapAlphaData = self._get_raw_bytes(-tag_end, unzip=True) def _handle_tag_definebitsjpeg3(self): """Handle the DefineBitsJPEG3 tag.""" obj = _make_object("DefineBitsJPEG3") self._generic_definebitsjpeg_parser(obj, 3) return obj def _handle_tag_definebitsjpeg4(self): """Handle the DefineBitsJPEG4 tag.""" obj = _make_object("DefineBitsJPEG4") self._generic_definebitsjpeg_parser(obj, 4) return obj def _generic_definebitslossless_parser(self, obj, version): """Generic parser for the DefineBitsLosslessN tags.""" tag_end = self._src.tell() + self._src.guard obj.CharacterID = unpack_ui16(self._src) obj.BitmapFormat = unpack_ui8(self._src) obj.BitmapWidth = unpack_ui16(self._src) obj.BitmapHeight = unpack_ui16(self._src) if 3 == obj.BitmapFormat: obj.BitmapColorTableSize = unpack_ui8(self._src) BitmapData = self._get_raw_bytes(-tag_end, unzip=True) _src = self._src try: self._src = io.BytesIO(BitmapData) if 3 == obj.BitmapFormat: if 1 == version: color = self._get_struct_rgb elif 2 == version: color = self._get_struct_rgba else: raise ValueError("unknown version: {}".format(version)) obj.ColorTableRGB = [ color() for _ in range(obj.BitmapColorTableSize + 1)] obj.ColormapPixelData = self._get_raw_bytes(-len(BitmapData)) elif obj.BitmapFormat in (4, 5): obj.BitmapPixelData = BitmapData else: raise ValueError("BitmapFormat: {}".format(obj.BitmapFormat)) finally: self._src = _src def _handle_tag_definebitslossless(self): """Handle the DefineBitsLossless tag.""" obj = _make_object("DefineBitsLossless") self._generic_definebitslossless_parser(obj, 1) return obj def _handle_tag_definebitslossless2(self): """Handle the DefineBitsLossless2 tag.""" obj = _make_object("DefineBitsLossless2") self._generic_definebitslossless_parser(obj, 2) return obj def _generic_definetext_parser(self, obj, rgb_struct): """Generic parser for the DefineTextN tags.""" obj.CharacterID = unpack_ui16(self._src) obj.TextBounds = self._get_struct_rect() obj.TextMatrix = self._get_struct_matrix() obj.GlyphBits = glyph_bits = unpack_ui8(self._src) obj.AdvanceBits = advance_bits = unpack_ui8(self._src) # textrecords obj.TextRecords = records = [] while True: endofrecords_flag = unpack_ui8(self._src) if endofrecords_flag == 0: # all done obj.EndOfRecordsFlag = 0 break # we have a TEXTRECORD, let's go back the 8 bits and set the obj self._src.seek(-1, io.SEEK_CUR) record = _make_object("TextRecord") records.append(record) bc = BitConsumer(self._src) record.TextRecordType = bc.u_get(1) record.StyleFlagsReserved = bc.u_get(3) record.StyleFlagsHasFont = bc.u_get(1) record.StyleFlagsHasColor = bc.u_get(1) record.StyleFlagsHasYOffset = bc.u_get(1) record.StyleFlagsHasXOffset = bc.u_get(1) if record.StyleFlagsHasFont: record.FontID = unpack_ui16(self._src) if record.StyleFlagsHasColor: record.TextColor = rgb_struct() if record.StyleFlagsHasXOffset: record.XOffset = unpack_si16(self._src) if record.StyleFlagsHasYOffset: record.YOffset = unpack_si16(self._src) if record.StyleFlagsHasFont: record.TextHeight = unpack_ui16(self._src) record.GlyphCount = unpack_ui8(self._src) bc = BitConsumer(self._src) record.GlyphEntries = glyphs = [] for _ in range(record.GlyphCount): glyph = _make_object("GlyphEntry") glyphs.append(glyph) glyph.GlyphIndex = bc.u_get(glyph_bits) glyph.GlyphAdvance = bc.u_get(advance_bits) def _handle_tag_definetext(self): """Handle the DefineText tag.""" obj = _make_object("DefineText") self._generic_definetext_parser(obj, self._get_struct_rgb) return obj def _handle_tag_definetext2(self): """Handle the DefineText2 tag.""" obj = _make_object("DefineText2") self._generic_definetext_parser(obj, self._get_struct_rgba) return obj def _handle_tag_defineedittext(self): """Handle the DefineEditText tag.""" obj = _make_object("DefineEditText") obj.CharacterID = unpack_ui16(self._src) obj.Bounds = self._get_struct_rect() bc = BitConsumer(self._src) obj.HasText = bc.u_get(1) obj.WordWrap = bc.u_get(1) obj.Multiline = bc.u_get(1) obj.Password = bc.u_get(1) obj.ReadOnly = bc.u_get(1) obj.HasTextColor = bc.u_get(1) obj.HasMaxLength = bc.u_get(1) obj.HasFont = bc.u_get(1) obj.HasFontClass = bc.u_get(1) obj.AutoSize = bc.u_get(1) obj.HasLayout = bc.u_get(1) obj.NoSelect = bc.u_get(1) obj.Border = bc.u_get(1) obj.WasStatic = bc.u_get(1) obj.HTML = bc.u_get(1) obj.UseOutlines = bc.u_get(1) if obj.HasFont: obj.FontID = unpack_ui16(self._src) if obj.HasFontClass: obj.FontClass = self._get_struct_string() if obj.HasFont: obj.FontHeight = unpack_ui16(self._src) if obj.HasTextColor: obj.TextColor = self._get_struct_rgba() if obj.HasMaxLength: obj.MaxLength = unpack_ui16(self._src) if obj.HasLayout: obj.Align = unpack_ui8(self._src) obj.LeftMargin = unpack_ui16(self._src) obj.RightMargin = unpack_ui16(self._src) obj.Indent = unpack_ui16(self._src) obj.Leading = unpack_ui16(self._src) obj.VariableName = self._get_struct_string() if obj.HasText: obj.InitialText = self._get_struct_string() return obj def _generic_placeobject_parser(self, obj, version): """A generic parser for several PlaceObjectX.""" bc = BitConsumer(self._src) obj.PlaceFlagHasClipActions = bc.u_get(1) obj.PlaceFlagHasClipDepth = bc.u_get(1) obj.PlaceFlagHasName = bc.u_get(1) obj.PlaceFlagHasRatio = bc.u_get(1) obj.PlaceFlagHasColorTransform = bc.u_get(1) obj.PlaceFlagHasMatrix = bc.u_get(1) obj.PlaceFlagHasCharacter = bc.u_get(1) obj.PlaceFlagMove = bc.u_get(1) if version == 3: obj.Reserved = bc.u_get(1) obj.PlaceFlagOpaqueBackground = bc.u_get(1) obj.PlaceFlagHasVisible = bc.u_get(1) obj.PlaceFlagHasImage = bc.u_get(1) obj.PlaceFlagHasClassName = bc.u_get(1) obj.PlaceFlagHasCacheAsBitmap = bc.u_get(1) obj.PlaceFlagHasBlendMode = bc.u_get(1) obj.PlaceFlagHasFilterList = bc.u_get(1) obj.Depth = unpack_ui16(self._src) if version == 3: if obj.PlaceFlagHasClassName or ( obj.PlaceFlagHasImage and obj.PlaceFlagHasCharacter): obj.ClassName = self._get_struct_string() if obj.PlaceFlagHasCharacter: obj.CharacterId = unpack_ui16(self._src) if obj.PlaceFlagHasMatrix: obj.Matrix = self._get_struct_matrix() if obj.PlaceFlagHasColorTransform: obj.ColorTransform = self._get_struct_cxformwithalpha() if obj.PlaceFlagHasRatio: obj.Ratio = unpack_ui16(self._src) if obj.PlaceFlagHasName: obj.Name = self._get_struct_string() if obj.PlaceFlagHasClipDepth: obj.ClipDepth = unpack_ui16(self._src) if version == 3: if obj.PlaceFlagHasFilterList: obj.SurfaceFilterList = self._get_struct_filterlist() if obj.PlaceFlagHasBlendMode: obj.BlendMode = unpack_ui8(self._src) if obj.PlaceFlagHasCacheAsBitmap: obj.BitmapCache = unpack_ui8(self._src) if obj.PlaceFlagHasVisible: obj.Visible = unpack_ui8(self._src) obj.BackgroundColor = self._get_struct_rgba() if obj.PlaceFlagHasClipActions: obj.ClipActions = self._get_struct_clipactions() def _handle_tag_placeobject2(self): """Handle the PlaceObject2 tag.""" obj = _make_object("PlaceObject2") self._generic_placeobject_parser(obj, 2) return obj def _handle_tag_placeobject3(self): """Handle the PlaceObject3 tag.""" obj = _make_object("PlaceObject3") self._generic_placeobject_parser(obj, 3) return obj def _handle_tag_definesprite(self): """Handle the DefineSprite tag.""" obj = _make_object("DefineSprite") obj.CharacterID = unpack_ui16(self._src) obj.FrameCount = unpack_ui16(self._src) tags = self._process_tags() obj.ControlTags = tags return obj def _generic_action_parser(self): """Generic parser for Actions.""" actions = [] while True: action_code = unpack_ui8(self._src) if action_code == 0: break action_name = ACTION_NAMES[action_code] if action_code > 128: # have a payload! action_len = unpack_ui16(self._src) try: action_meth = getattr( self, "_handle_" + action_name.lower()) except AttributeError: if self.unknown_alert: raise ValueError( "Unknown action: " + repr(action_name)) action_payload = self._src.read(action_len) _dict = {'__str__': _repr, '__repr__': _repr, 'name': action_name} action = type("UnknownAction", (SWFObject,), _dict)() action.raw_payload = action_payload actions.append(action) else: prev_pos = self._src.tell() for action in action_meth(action_len): assert action is not None, action_name actions.append(action) quant_read = self._src.tell() - prev_pos if quant_read != action_len: raise RuntimeError( "Bad bytes consumption by action {!r} handler " "(did {}, should {})".format( action_name, quant_read, action_len)) else: action = _make_object(action_name) actions.append(action) return actions def _handle_tag_doaction(self): """Handle the DoAction tag.""" obj = _make_object("DoAction") obj.Actions = self._generic_action_parser() return obj def _handle_tag_fileattributes(self): """Handle the FileAttributes tag.""" obj = _make_object("FileAttributes") bc = BitConsumer(self._src) bc.u_get(1) # reserved obj.UseDirectBlit = bc.u_get(1) obj.UseGPU = bc.u_get(1) obj.HasMetadata = bc.u_get(1) obj.ActionScript3 = bc.u_get(1) bc.u_get(2) # reserved obj.UseNetwork = bc.u_get(1) bc.u_get(24) # reserved return obj def _handle_tag_metadata(self): """Handle the Metadata tag.""" obj = _make_object("Metadata") obj.Metadata = self._get_struct_string() return obj def _handle_tag_setbackgroundcolor(self): """Handle the SetBackgroundColor tag.""" obj = _make_object("SetBackgroundColor") obj.BackgroundColor = self._get_struct_rgb() return obj def _handle_tag_definesceneandframelabeldata(self): """Handle the DefineSceneAndFrameLabelData tag.""" obj = _make_object("DefineSceneAndFrameLabelData") obj.SceneCount = self._get_struct_encodedu32() for i in range(1, obj.SceneCount + 1): setattr(obj, 'Offset{}'.format(i), self._get_struct_encodedu32()) setattr(obj, 'Name{}'.format(i), self._get_struct_string()) obj.FrameLabelCount = self._get_struct_encodedu32() for i in range(1, obj.FrameLabelCount + 1): setattr(obj, 'FrameNum{}'.format(i), self._get_struct_encodedu32()) setattr(obj, 'FrameLabel{}'.format(i), self._get_struct_string()) return obj def _handle_tag_defineshape4(self): """Handle the DefineShape4 tag.""" obj = _make_object("DefineShape4") obj.ShapeId = unpack_ui16(self._src) obj.ShapeBounds = self._get_struct_rect() obj.EdgeBounds = self._get_struct_rect() bc = BitConsumer(self._src) bc.u_get(5) # reserved obj.UsesFillWindingRule = bc.u_get(1) obj.UsesNonScalingStrokes = bc.u_get(1) obj.UsesScalingStrokes = bc.u_get(1) obj.Shapes = self._get_struct_shapewithstyle(4) return obj def _handle_tag_definemorphshape2(self): """Handle the DefineMorphShape2 tag.""" obj = _make_object("DefineMorphShape2") obj.CharacterId = unpack_ui16(self._src) obj.StartBounds = self._get_struct_rect() obj.EndBounds = self._get_struct_rect() obj.StartEdgeBounds = self._get_struct_rect() obj.EndEdgeBounds = self._get_struct_rect() bc = BitConsumer(self._src) bc.u_get(6) # reserved obj.UsesNonScalingStrokes = bc.u_get(1) obj.UsesScalingStrokes = bc.u_get(1) obj.Offset = unpack_ui32(self._src) # FIXME: this tag needs more work; I'm skipping some attributes here self._src.read(obj.Offset) obj.EndEdges = self._get_struct_shape() return obj def _handle_tag_showframe(self): """Handle the ShowFrame tag.""" return _make_object("ShowFrame") def _handle_tag_removeobject(self): """Handle the RemoveObject tag.""" obj = _make_object("RemoveObject") obj.CharacterId = unpack_ui16(self._src) obj.Depth = unpack_ui16(self._src) return obj def _handle_tag_removeobject2(self): """Handle the RemoveObject2 tag.""" obj = _make_object("RemoveObject2") obj.Depth = unpack_ui16(self._src) return obj def _handle_tag_defineshape(self): """Handle the DefineShape tag.""" obj = _make_object("DefineShape") obj.ShapeId = unpack_ui16(self._src) obj.ShapeBounds = self._get_struct_rect() obj.Shapes = self._get_struct_shapewithstyle(1) return obj def _handle_tag_defineshape2(self): """Handle the DefineShape2 tag.""" obj = _make_object("DefineShape2") obj.ShapeId = unpack_ui16(self._src) obj.ShapeBounds = self._get_struct_rect() obj.Shapes = self._get_struct_shapewithstyle(2) return obj def _handle_tag_defineshape3(self): """Handle the DefineShape3 tag.""" obj = _make_object("DefineShape3") obj.ShapeId = unpack_ui16(self._src) obj.ShapeBounds = self._get_struct_rect() obj.Shapes = self._get_struct_shapewithstyle(3) return obj def _generic_definefont_parser(self, obj): """A generic parser for several DefineFontX.""" obj.FontID = unpack_ui16(self._src) bc = BitConsumer(self._src) obj.FontFlagsHasLayout = bc.u_get(1) obj.FontFlagsShiftJIS = bc.u_get(1) obj.FontFlagsSmallText = bc.u_get(1) obj.FontFlagsANSI = bc.u_get(1) obj.FontFlagsWideOffsets = bc.u_get(1) obj.FontFlagsWideCodes = bc.u_get(1) obj.FontFlagsItalic = bc.u_get(1) obj.FontFlagsBold = bc.u_get(1) obj.LanguageCode = self._get_struct_langcode() obj.FontNameLen = unpack_ui8(self._src) obj.FontName = "".join(chr(unpack_ui8(self._src)) for i in range(obj.FontNameLen)) if obj.FontName[-1] == '\x00': # most probably ends in null, clean it obj.FontName = obj.FontName[:-1] obj.NumGlyphs = num_glyphs = unpack_ui16(self._src) self._last_defined_glyphs_quantity = num_glyphs getter_wide = unpack_ui32 if obj.FontFlagsWideOffsets else unpack_ui16 obj.OffsetTable = [getter_wide(self._src) for _ in range(num_glyphs)] obj.CodeTableOffset = getter_wide(self._src) obj.GlyphShapeTable = [self._get_struct_shape() for _ in range(num_glyphs)] obj.CodeTable = [unpack_ui16(self._src) for _ in range(num_glyphs)] if obj.FontFlagsHasLayout: obj.FontAscent = unpack_ui16(self._src) obj.FontDecent = unpack_ui16(self._src) obj.FontLeading = unpack_ui16(self._src) obj.FontAdvanceTable = [unpack_si16(self._src) for _ in range(num_glyphs)] obj.FontBoundsTable = [self._get_struct_rect() for _ in range(num_glyphs)] obj.KerningCount = unpack_ui16(self._src) obj.FontKerningTable = [ self._get_struct_kerningrecord(obj.FontFlagsWideCodes) for _ in range(obj.KerningCount)] def _handle_tag_definefont2(self): """Handle the DefineFont2 tag.""" obj = _make_object("DefineFont2") self._generic_definefont_parser(obj) return obj def _handle_tag_definefont3(self): """Handle the DefineFont3 tag.""" obj = _make_object("DefineFont3") self._generic_definefont_parser(obj) return obj def _handle_tag_definebutton2(self): """Handle the DefineButton2 tag.""" obj = _make_object("DefineButton2") obj.ButtonId = unpack_ui16(self._src) bc = BitConsumer(self._src) bc.ReservedFlags = bc.u_get(7) bc.TrackAsMenu = bc.u_get(1) obj.ActionOffset = unpack_ui16(self._src) # characters obj.Characters = characters = [] while True: end_flag = unpack_ui8(self._src) if end_flag == 0: # all done obj.CharacterEndFlag = 0 break # we have a BUTTONRECORD, let's go back the 8 bits and set the obj self._src.seek(-1, io.SEEK_CUR) character = _make_object("ButtonRecord") characters.append(character) bc = BitConsumer(self._src) character.ButtonReserved = bc.u_get(2) character.ButtonHasBlendMode = bc.u_get(1) character.ButtonHasFilterList = bc.u_get(1) character.ButtonStateHitTest = bc.u_get(1) character.ButtonStateDown = bc.u_get(1) character.ButtonStateOver = bc.u_get(1) character.ButtonStateUp = bc.u_get(1) character.CharacterId = unpack_ui16(self._src) character.PlaceDepth = unpack_ui16(self._src) character.PlaceMatrix = self._get_struct_matrix() character.ColorTransform = self._get_struct_cxformwithalpha() if character.ButtonHasFilterList: character.FilterList = self._get_struct_filterlist() if character.ButtonHasBlendMode: character.BlendMode = unpack_ui8(self._src) obj.Actions = actions = [] still_have_actions = True while still_have_actions: end_flag = unpack_ui16(self._src) if end_flag == 0: # this is the last action, parse it and then exit still_have_actions = False bca = _make_object("ButtonCondAction") actions.append(bca) bca.CondActionSize = end_flag bc = BitConsumer(self._src) bca.CondIdleToOverDown = bc.u_get(1) bca.CondOutDownToIdle = bc.u_get(1) bca.CondOutDownToOverDown = bc.u_get(1) bca.CondOverDownToOutDown = bc.u_get(1) bca.CondOverDownToOverUp = bc.u_get(1) bca.CondOverUpToOverDown = bc.u_get(1) bca.CondOverUpToIdle = bc.u_get(1) bca.CondIdleToOverUp = bc.u_get(1) bca.CondKeyPress = bc.u_get(7) bca.CondOverDownToIdle = bc.u_get(1) bca.Actions = self._generic_action_parser() return obj def _handle_tag_enabledebugger2(self): """Handle the EnableDebugger2 tag.""" obj = _make_object("EnableDebugger2") obj.Reserved = unpack_ui16(self._src) obj.Password = self._get_struct_string() return obj def _handle_tag_scriptlimits(self): """Handle the ScriptLimits tag.""" obj = _make_object("ScriptLimits") obj.MaxRecursionDepth = unpack_ui16(self._src) obj.ScriptTimeoutSeconds = unpack_ui16(self._src) return obj def _handle_tag_framelabel(self): """Handle the FrameLabel tag.""" obj = _make_object("FrameLabel") obj.Name = self._get_struct_string() return obj def _handle_tag_jpegtables(self): """Handle the JPEGTables tag.""" obj = _make_object("JPEGTables") assert self._src.read(2) == b'\xFF\xD8' # SOI marker eoimark1 = eoimark2 = None allbytes = [b'\xFF\xD8'] while not (eoimark1 == b'\xFF' and eoimark2 == b'\xD9'): newbyte = self._src.read(1) allbytes.append(newbyte) eoimark1 = eoimark2 eoimark2 = newbyte # concatenate everything, removing the end mark obj.JPEGData = b"".join(allbytes[:-2]) return obj def _handle_tag_definefontalignzones(self): """Handle the DefineFontAlignZones tag.""" obj = _make_object("DefineFontAlignZones") obj.FontId = unpack_ui16(self._src) bc = BitConsumer(self._src) obj.CSMTableHint = bc.u_get(2) obj.Reserved = bc.u_get(6) obj.ZoneTable = zone_records = [] glyph_count = self._last_defined_glyphs_quantity self._last_defined_glyphs_quantity = None for _ in range(glyph_count): zone_record = _make_object("ZoneRecord") zone_records.append(zone_record) zone_record.NumZoneData = unpack_ui8(self._src) zone_record.ZoneData = zone_data = [] for _ in range(zone_record.NumZoneData): zone_datum = _make_object("ZoneData") zone_data.append(zone_datum) zone_datum.AlignmentCoordinate = unpack_float16(self._src) zone_datum.Range = unpack_float16(self._src) bc = BitConsumer(self._src) zone_record.Reserved = bc.u_get(6) zone_record.ZoneMaskY = bc.u_get(1) zone_record.ZoneMaskX = bc.u_get(1) return obj def _handle_tag_definefontname(self): """Handle the DefineFontName tag.""" obj = _make_object("DefineFontName") obj.FontId = unpack_ui16(self._src) obj.FontName = self._get_struct_string() obj.FontCopyright = self._get_struct_string() return obj def _handle_tag_csmtextsettings(self): """Handle the CSMTextSettings tag.""" obj = _make_object("CSMTextSettings") obj.TextId = unpack_ui16(self._src) bc = BitConsumer(self._src) obj.UseFlashType = bc.u_get(2) obj.GridFit = bc.u_get(3) obj.Reserved1 = bc.u_get(3) obj.Thickness = unpack_float(self._src) obj.Sharpness = unpack_float(self._src) obj.Reserved2 = unpack_ui8(self._src) return obj def _get_raw_bytes(self, size, unzip=False): '''Get raw bytes data, optional uncompress with ZLIB''' pos = self._src.tell() try: # < 0: read until this pos if size < 0: assert abs(size) > pos size = abs(size) - pos data = self._src.read(size) if unzip: return zlib.decompress(data) else: return data except Exception: self._src.seek(pos, io.SEEK_SET) raise def _get_struct_rect(self): """Get the RECT structure.""" bc = BitConsumer(self._src) nbits = bc.u_get(5) if self._read_twips: return tuple(bc.s_get(nbits) for _ in range(4)) else: return tuple(bc.s_get(nbits) / 20.0 for _ in range(4)) def _get_struct_rgb(self): """Get the RGB structure.""" return [unpack_ui8(self._src) for _ in range(3)] def _get_struct_rgba(self): """Get the RGBA structure.""" return [unpack_ui8(self._src) for _ in range(4)] def _get_struct_langcode(self): """Get the LANGCODE structure.""" code = unpack_ui8(self._src) return LANGCODES[code] def _get_struct_kerningrecord(self, font_flags_wide_codes): """Get the KERNINGRECORD structure.""" getter = unpack_ui16 if font_flags_wide_codes else unpack_ui8 data = {} data['FontKerningCode1'] = getter(self._src) data['FontKerningCode2'] = getter(self._src) data['FontKerningAdjustment'] = unpack_si16(self._src) return data def _get_struct_clipactions(self): """Get the several CLIPACTIONRECORDs.""" obj = _make_object("ClipActions") # In SWF 5 and earlier, these are 2 bytes wide; in SWF 6 # and later 4 bytes clipeventflags_size = 2 if self._version <= 5 else 4 clipactionend_size = 2 if self._version <= 5 else 4 all_zero = b"\x00" * clipactionend_size assert unpack_ui16(self._src) == 0 # reserved obj.AllEventFlags = self._src.read(clipeventflags_size) obj.ClipActionRecords = records = [] while True: next_bytes = self._src.read(clipactionend_size) if next_bytes == all_zero: # was the ClipActionEndFlag return record = _make_object("ClipActionRecord") records.append(record) # as event flags and end flag has same size, we can do this trick record.EventFlags = next_bytes record.ActionRecordSize = unpack_ui32(self._src) record.TheRestTODO = self._src.read(record.ActionRecordSize) # FIXME: this struct needs more work; the EventFlags should be # expanded and each ActionRecord(s) should be detailed more return obj def _get_struct_string(self): """Get the STRING structure.""" data = [] while True: t = self._src.read(1) if t == b'\x00': break data.append(t) val = b''.join(data) return val.decode("utf8") def _get_struct_matrix(self): """Get the values for the MATRIX record.""" obj = _make_object("Matrix") bc = BitConsumer(self._src) # scale obj.HasScale = bc.u_get(1) if obj.HasScale: obj.NScaleBits = n_scale_bits = bc.u_get(5) obj.ScaleX = bc.fb_get(n_scale_bits) obj.ScaleY = bc.fb_get(n_scale_bits) # rotate obj.HasRotate = bc.u_get(1) if obj.HasRotate: obj.NRotateBits = n_rotate_bits = bc.u_get(5) obj.RotateSkew0 = bc.fb_get(n_rotate_bits) obj.RotateSkew1 = bc.fb_get(n_rotate_bits) # translate obj.NTranslateBits = n_translate_bits = bc.u_get(5) obj.TranslateX = bc.s_get(n_translate_bits) obj.TranslateY = bc.s_get(n_translate_bits) if not self._read_twips: obj.TranslateX /= 20.0 obj.TranslateY /= 20.0 return obj def _get_struct_cxformwithalpha(self): """Get the values for the CXFORMWITHALPHA record.""" obj = _make_object("CXformWithAlpha") bc = BitConsumer(self._src) obj.HasAddTerms = bc.u_get(1) obj.HasMultTerms = bc.u_get(1) obj.NBits = nbits = bc.u_get(4) if obj.HasMultTerms: obj.RedMultTerm = bc.s_get(nbits) obj.GreenMultTerm = bc.s_get(nbits) obj.BlueMultTerm = bc.s_get(nbits) obj.AlphaMultTerm = bc.s_get(nbits) if obj.HasAddTerms: obj.RedAddTerm = bc.s_get(nbits) obj.GreenAddTerm = bc.s_get(nbits) obj.BlueAddTerm = bc.s_get(nbits) obj.AlphaAddTerm = bc.s_get(nbits) return obj def _get_shaperecords(self, num_fill_bits, num_line_bits, shape_number): """Return an array of SHAPERECORDS.""" shape_records = [] bc = BitConsumer(self._src) while True: type_flag = bc.u_get(1) if type_flag: # edge record straight_flag = bc.u_get(1) num_bits = bc.u_get(4) if straight_flag: record = _make_object('StraightEdgeRecord') record.TypeFlag = 1 record.StraightFlag = 1 record.NumBits = num_bits record.GeneralLineFlag = general_line_flag = bc.u_get(1) if general_line_flag: record.DeltaX = bc.s_get(num_bits + 2) record.DeltaY = bc.s_get(num_bits + 2) else: record.VertLineFlag = vert_line_flag = bc.s_get(1) if vert_line_flag: record.DeltaY = bc.s_get(num_bits + 2) else: record.DeltaX = bc.s_get(num_bits + 2) else: record = _make_object('CurvedEdgeRecord') record.TypeFlag = 1 record.StraightFlag = 0 record.NumBits = num_bits record.ControlDeltaX = bc.s_get(num_bits + 2) record.ControlDeltaY = bc.s_get(num_bits + 2) record.AnchorDeltaX = bc.s_get(num_bits + 2) record.AnchorDeltaY = bc.s_get(num_bits + 2) else: # non edge record record = _make_object('StyleChangeRecord') record.TypeFlag = 0 five_bits = [bc.u_get(1) for _ in range(5)] if not any(five_bits): # the five bits are zero, this is an EndShapeRecord break # we're not done, store the proper flags (record.StateNewStyles, record.StateLineStyle, record.StateFillStyle1, record.StateFillStyle0, record.StateMoveTo) = five_bits if record.StateMoveTo: record.MoveBits = move_bits = bc.u_get(5) record.MoveDeltaX = bc.s_get(move_bits) record.MoveDeltaY = bc.s_get(move_bits) if record.StateFillStyle0: record.FillStyle0 = bc.u_get(num_fill_bits) if record.StateFillStyle1: record.FillStyle1 = bc.u_get(num_fill_bits) if record.StateLineStyle: record.LineStyle = bc.u_get(num_line_bits) if record.StateNewStyles: record.FillStyles = self._get_struct_fillstylearray( shape_number) record.LineStyles = self._get_struct_linestylearray( shape_number) # these two not only belong to the record, but also # modifies the number of bits read in the future # if shape number bigs enough (didn't find this in the # spec, but works for now, maybe '2' is not the limit...) if shape_number > 2: record.NumFillBits = num_fill_bits = bc.u_get(4) record.NumLineBits = num_line_bits = bc.u_get(4) else: record.NumFillBits = bc.u_get(4) record.NumLineBits = bc.u_get(4) # reset the BC here, as the structures just read work at # byte level bc = BitConsumer(self._src) shape_records.append(record) return shape_records def _get_struct_shape(self): """Get the values for the SHAPE record.""" obj = _make_object("Shape") bc = BitConsumer(self._src) obj.NumFillBits = n_fill_bits = bc.u_get(4) obj.NumLineBits = n_line_bits = bc.u_get(4) obj.ShapeRecords = self._get_shaperecords( n_fill_bits, n_line_bits, 0) return obj def _get_struct_fillstyle(self, shape_number): """Get the values for the FILLSTYLE record.""" obj = _make_object("FillStyle") obj.FillStyleType = style_type = unpack_ui8(self._src) if style_type == 0x00: if shape_number <= 2: obj.Color = self._get_struct_rgb() else: obj.Color = self._get_struct_rgba() if style_type in (0x10, 0x12, 0x13): obj.GradientMatrix = self._get_struct_matrix() if style_type in (0x10, 0x12): obj.Gradient = self._get_struct_gradient(shape_number) if style_type == 0x13: obj.Gradient = self._get_struct_focalgradient(shape_number) if style_type in (0x40, 0x41, 0x42, 0x43): obj.BitmapId = unpack_ui16(self._src) obj.BitmapMatrix = self._get_struct_matrix() return obj def _get_struct_fillstylearray(self, shape_number): """Get the values for the FILLSTYLEARRAY record.""" obj = _make_object("FillStyleArray") obj.FillStyleCount = count = unpack_ui8(self._src) if count == 0xFF: obj.FillStyleCountExtended = count = unpack_ui16(self._src) obj.FillStyles = [self._get_struct_fillstyle(shape_number) for _ in range(count)] return obj def _get_struct_linestylearray(self, shape_number): """Get the values for the LINESTYLEARRAY record.""" obj = _make_object("LineStyleArray") obj.LineStyleCount = count = unpack_ui8(self._src) if count == 0xFF: obj.LineStyleCountExtended = count = unpack_ui16(self._src) obj.LineStyles = line_styles = [] for _ in range(count): if shape_number <= 3: record = _make_object("LineStyle") record.Width = unpack_ui16(self._src) if shape_number <= 2: record.Color = self._get_struct_rgb() else: record.Color = self._get_struct_rgba() else: record = _make_object("LineStyle2") record.Width = unpack_ui16(self._src) bc = BitConsumer(self._src) record.StartCapStyle = bc.u_get(2) record.JoinStyle = bc.u_get(2) record.HasFillFlag = bc.u_get(1) record.NoHScaleFlag = bc.u_get(1) record.NoVScaleFlag = bc.u_get(1) record.PixelHintingFlag = bc.u_get(1) bc.u_get(5) # reserved record.NoClose = bc.u_get(1) record.EndCapStyle = bc.u_get(2) if record.JoinStyle == 2: record.MiterLimitFactor = unpack_ui16(self._src) if record.HasFillFlag == 0: record.Color = self._get_struct_rgba() else: record.Color = self._get_struct_fillstyle(shape_number) line_styles.append(record) return obj def _get_struct_encodedu32(self): """Get a EncodedU32 number.""" useful = [] while True: byte = ord(self._src.read(1)) useful.append(byte) if byte < 127: # got all the useful bytes break # transform into bits reordering the bytes useful = ['00000000' + bin(b)[2:] for b in useful[::-1]] # get the top 7 (*seven*, as the eight one is the flag) and convert return int(''.join([b[-7:] for b in useful]), 2) def _get_struct_shapewithstyle(self, shape_number): """Get the values for the SHAPEWITHSTYLE record.""" obj = _make_object("ShapeWithStyle") obj.FillStyles = self._get_struct_fillstylearray(shape_number) obj.LineStyles = self._get_struct_linestylearray(shape_number) bc = BitConsumer(self._src) obj.NumFillBits = n_fill_bits = bc.u_get(4) obj.NumlineBits = n_line_bits = bc.u_get(4) obj.ShapeRecords = self._get_shaperecords( n_fill_bits, n_line_bits, shape_number) return obj def _get_struct_gradient(self, shape_number): """Get the values for the GRADIENT record.""" obj = _make_object("Gradient") bc = BitConsumer(self._src) obj.SpreadMode = bc.u_get(2) obj.InterpolationMode = bc.u_get(2) obj.NumGradients = bc.u_get(4) obj.GradientRecords = gradient_records = [] for _ in range(obj.NumGradients): record = _make_object("GradRecord") gradient_records.append(record) record.Ratio = unpack_ui8(self._src) if shape_number <= 2: record.Color = self._get_struct_rgb() else: record.Color = self._get_struct_rgba() return obj def _get_struct_focalgradient(self, shape_number): """Get the values for the FOCALGRADIENT record.""" obj = _make_object("FocalGradient") bc = BitConsumer(self._src) obj.SpreadMode = bc.u_get(2) obj.InterpolationMode = bc.u_get(2) obj.NumGradients = bc.u_get(4) obj.GradientRecords = gradient_records = [] for _ in range(obj.NumGradients): record = _make_object("GradRecord") gradient_records.append(record) record.Ratio = unpack_ui8(self._src) if shape_number <= 2: record.Color = self._get_struct_rgb() else: record.Color = self._get_struct_rgba() obj.FocalPoint = unpack_fixed8(self._src) return obj def _get_struct_filterlist(self): """Get the values for the FILTERLIST record.""" obj = _make_object("FilterList") obj.NumberOfFilters = unpack_ui8(self._src) obj.Filter = filters = [] # how to decode each filter type (and name), according to the filter id filter_type = [ ("DropShadowFilter", self._get_struct_dropshadowfilter), # 0 ("BlurFilter", self._get_struct_blurfilter), # 1 ("GlowFilter", self._get_struct_glowfilter), # 2... ("BevelFilter", self._get_struct_bevelfilter), ("GradientGlowFilter", self._get_struct_gradientglowfilter), ("ConvolutionFilter", self._get_struct_convolutionfilter), ("ColorMatrixFilter", self._get_struct_colormatrixfilter), ("GradientBevelFilter", self._get_struct_gradientbevelfilter), # 7 ] for _ in range(obj.NumberOfFilters): _filter = _make_object("Filter") filters.append(_filter) _filter.FilterId = unpack_ui8(self._src) name, func = filter_type[_filter.FilterId] setattr(_filter, name, func()) def _get_struct_dropshadowfilter(self): """Get the values for the DROPSHADOWFILTER record.""" obj = _make_object("DropShadowFilter") obj.DropShadowColor = self._get_struct_rgba() obj.BlurX = unpack_fixed16(self._src) obj.BlurY = unpack_fixed16(self._src) obj.Angle = unpack_fixed16(self._src) obj.Distance = unpack_fixed16(self._src) obj.Strength = unpack_fixed8(self._src) bc = BitConsumer(self._src) obj.InnerShadow = bc.u_get(1) obj.Knockout = bc.u_get(1) obj.CompositeSource = bc.u_get(1) obj.Passes = bc.u_get(5) return obj def _get_struct_blurfilter(self): """Get the values for the BLURFILTER record.""" obj = _make_object("BlurFilter") obj.BlurX = unpack_fixed16(self._src) obj.BlurY = unpack_fixed16(self._src) bc = BitConsumer(self._src) obj.Passes = bc.u_get(5) obj.Reserved = bc.u_get(3) return obj def _get_struct_glowfilter(self): """Get the values for the GLOWFILTER record.""" obj = _make_object("GlowFilter") obj.GlowColor = self._get_struct_rgba() obj.BlurX = unpack_fixed16(self._src) obj.BlurY = unpack_fixed16(self._src) obj.Strength = unpack_fixed8(self._src) bc = BitConsumer(self._src) obj.InnerGlow = bc.u_get(1) obj.Knockout = bc.u_get(1) obj.CompositeSource = bc.u_get(1) obj.Passes = bc.u_get(5) return obj def _get_struct_bevelfilter(self): """Get the values for the BEVELFILTER record.""" obj = _make_object("BevelFilter") obj.ShadowColor = self._get_struct_rgba() obj.HighlightColor = self._get_struct_rgba() obj.BlurX = unpack_fixed16(self._src) obj.BlurY = unpack_fixed16(self._src) obj.Angle = unpack_fixed16(self._src) obj.Distance = unpack_fixed16(self._src) obj.Strength = unpack_fixed8(self._src) bc = BitConsumer(self._src) obj.InnerShadow = bc.u_get(1) obj.Knockout = bc.u_get(1) obj.CompositeSource = bc.u_get(1) obj.OnTop = bc.u_get(1) obj.Passes = bc.u_get(4) return obj def _get_struct_gradientglowfilter(self): """Get the values for the GRADIENTGLOWFILTER record.""" obj = _make_object("GradientGlowFilter") obj.NumColors = num_colors = unpack_ui8(self._src) obj.GradientColors = [self._get_struct_rgba() for _ in range(num_colors)] obj.GradientRatio = [unpack_ui8(self._src) for _ in range(num_colors)] obj.BlurX = unpack_fixed16(self._src) obj.BlurY = unpack_fixed16(self._src) obj.Angle = unpack_fixed16(self._src) obj.Distance = unpack_fixed16(self._src) obj.Strength = unpack_fixed8(self._src) bc = BitConsumer(self._src) obj.InnerShadow = bc.u_get(1) obj.Knockout = bc.u_get(1) obj.CompositeSource = bc.u_get(1) obj.OnTop = bc.u_get(1) obj.Passes = bc.u_get(4) return obj def _get_struct_convolutionfilter(self): """Get the values for the CONVOLUTIONFILTER record.""" obj = _make_object("ConvolutionFilter") obj.MatrixX = unpack_ui8(self._src) obj.MatrixY = unpack_ui8(self._src) obj.Divisor = unpack_float(self._src) obj.Bias = unpack_float(self._src) _quant = obj.MatrixX * obj.MatrixY obj.Matrix = [unpack_float(self._src) for _ in range(_quant)] obj.DefaultColor = self._get_struct_rgba() bc = BitConsumer(self._src) obj.Reserved = bc.u_get(6) obj.Clamp = bc.u_get(1) obj.PreserveAlpha = bc.u_get(1) return obj def _get_struct_colormatrixfilter(self): """Get the values for the COLORMATRIXFILTER record.""" obj = _make_object("ColorMatrixFilter") obj.Matrix = [unpack_float(self._src) for _ in range(20)] return obj def _get_struct_gradientbevelfilter(self): """Get the values for the GRADIENTBEVELFILTER record.""" obj = _make_object("GradientBevelFilter") obj.NumColors = num_colors = unpack_ui8(self._src) obj.GradientColors = [self._get_struct_rgba() for _ in range(num_colors)] obj.GradientRatio = [unpack_ui8(self._src) for _ in range(num_colors)] obj.BlurX = unpack_fixed16(self._src) obj.BlurY = unpack_fixed16(self._src) obj.Angle = unpack_fixed16(self._src) obj.Distance = unpack_fixed16(self._src) obj.Strength = unpack_fixed8(self._src) bc = BitConsumer(self._src) obj.InnerShadow = bc.u_get(1) obj.Knockout = bc.u_get(1) obj.CompositeSource = bc.u_get(1) obj.OnTop = bc.u_get(1) obj.Passes = bc.u_get(4) return obj def _handle_actionconstantpool(self, _): """Handle the ActionConstantPool action.""" obj = _make_object("ActionConstantPool") obj.Count = count = unpack_ui16(self._src) obj.ConstantPool = pool = [] for _ in range(count): pool.append(self._get_struct_string()) yield obj def _handle_actiongeturl(self, _): """Handle the ActionGetURL action.""" obj = _make_object("ActionGetURL") obj.UrlString = self._get_struct_string() obj.TargetString = self._get_struct_string() yield obj def _handle_actionpush(self, length): """Handle the ActionPush action.""" init_pos = self._src.tell() while self._src.tell() < init_pos + length: obj = _make_object("ActionPush") obj.Type = unpack_ui8(self._src) # name and how to read each type push_types = { 0: ("String", self._get_struct_string), 1: ("Float", lambda: unpack_float(self._src)), 2: ("Null", lambda: None), 4: ("RegisterNumber", lambda: unpack_ui8(self._src)), 5: ("Boolean", lambda: unpack_ui8(self._src)), 6: ("Double", lambda: unpack_double(self._src)), 7: ("Integer", lambda: unpack_ui32(self._src)), 8: ("Constant8", lambda: unpack_ui8(self._src)), 9: ("Constant16", lambda: unpack_ui16(self._src)), } name, func = push_types[obj.Type] setattr(obj, name, func()) yield obj def _handle_actiondefinefunction(self, _): """Handle the ActionDefineFunction action.""" obj = _make_object("ActionDefineFunction") obj.FunctionName = self._get_struct_string() obj.NumParams = unpack_ui16(self._src) for i in range(1, obj.NumParams + 1): setattr(obj, "param" + str(i), self._get_struct_string()) obj.CodeSize = unpack_ui16(self._src) yield obj def _handle_actionif(self, _): """Handle the ActionIf action.""" obj = _make_object("ActionIf") obj.BranchOffset = unpack_si16(self._src) yield obj def _handle_actiondefinefunction2(self, _): """Handle the ActionDefineFunction2 action.""" obj = _make_object("ActionDefineFunction2") obj.FunctionName = self._get_struct_string() obj.NumParams = unpack_ui16(self._src) obj.RegisterCount = unpack_ui8(self._src) bc = BitConsumer(self._src) obj.PreloadParentFlag = bc.u_get(1) obj.PreloadRootFlag = bc.u_get(1) obj.SupressSuperFlag = bc.u_get(1) obj.PreloadSuperFlag = bc.u_get(1) obj.SupressArgumentsFlag = bc.u_get(1) obj.PreloadArgumentsFlag = bc.u_get(1) obj.SupressThisFlag = bc.u_get(1) obj.PreloadThisFlag = bc.u_get(1) obj.Reserved = bc.u_get(7) obj.PreloadGlobalFlag = bc.u_get(1) obj.Parameters = parameters = [] for _ in range(obj.NumParams): parameter = _make_object("Parameter") parameters.append(parameter) parameter.Register = unpack_ui8(self._src) parameter.ParamName = self._get_struct_string() obj.CodeSize = unpack_ui16(self._src) yield obj def coverage(self): """Calculate the coverage of a file.""" items_unk = collections.Counter() items_ok = collections.Counter() def _go_deep(obj): """Recursive function to find internal attributes.""" if type(obj).__name__ in ('UnknownObject', 'UnknownAction'): # blatantly unknown items_unk[obj.name] += 1 elif obj.name in ('DefineMorphShape2', 'ClipActions'): # these are incomplete, see FIXMEs in the code above items_unk[obj.name] += 1 else: # fully parsed items_ok[obj.name] += 1 for name in obj._attribs: attr = getattr(obj, name) if isinstance(attr, SWFObject): _go_deep(attr) for tag in self.tags: _go_deep(tag) full_count = sum(items_ok.values()) + sum(items_unk.values()) coverage = 100 * sum(items_ok.values()) / full_count print("Coverage is {:.1f}% of {} total items".format(coverage, full_count)) print("Most common parsed objects:") for k, v in items_ok.most_common(3): print("{:5d} {}".format(v, k)) if items_unk: print("Most common Unknown objects") for k, v in items_unk.most_common(3): print("{:5d} {}".format(v, k)) def parsefile(filename, read_twips=True): """Parse a SWF. If you have a file object already, just use SWFParser directly. read_twips: True - return values as read from the SWF False - return values in pixels (at 100% zoom) """ with open(filename, 'rb') as fh: return SWFParser(fh, read_twips) if __name__ == "__main__": import cv2 import numpy as np import time import traceback from PIL import Image from format_convert.utils import pil2np start_time = time.time() p = "C:/Users/Administrator/Downloads/13035f4a379c4d24b89835456e047c14.swf" # p = "C:/Users/Administrator/Desktop/test_swf/error1.swf" swf_parser = parsefile(p) index = 0 for tag in swf_parser.tags: try: if not hasattr(tag, 'ImageData'): continue byte_data = tag.ImageData with open('images/' + str(index) + '.png', 'wb') as f: f.write(byte_data) # with open('images/' + str(index) + '.txt', 'w') as f: # f.write(str(byte_data)) image = Image.open('images/' + str(index) + '.png') # image_np = pil2np(image) print(index, image.size) if image.size[0] > 1000 and image.size[1] > 1000: image = image.resize((600, 1000), Image.BILINEAR) image.save('images/' + str(index) + '.png', quality=10, ) # # with open('images/' + str(index) + '.png', 'rb') as f: # byte_data = f.read() # with open('images/' + str(index) + '.txt', 'w') as f: # f.write(str(byte_data)) except: traceback.print_exc() index += 1 print(time.time()-start_time)