|
@@ -0,0 +1,2655 @@
|
|
|
+from .consts import *
|
|
|
+from .data import *
|
|
|
+from .utils import *
|
|
|
+from .stream import *
|
|
|
+try:
|
|
|
+ import Image
|
|
|
+except ImportError:
|
|
|
+ from PIL import Image
|
|
|
+import struct
|
|
|
+
|
|
|
+try:
|
|
|
+ import cStringIO as StringIO
|
|
|
+except ImportError:
|
|
|
+ from io import BytesIO
|
|
|
+
|
|
|
+class TagFactory(object):
|
|
|
+ @classmethod
|
|
|
+ def create(cls, type):
|
|
|
+ """ Return the created tag by specifying an integer """
|
|
|
+ if type == 0: return TagEnd()
|
|
|
+ elif type == 1: return TagShowFrame()
|
|
|
+ elif type == 2: return TagDefineShape()
|
|
|
+ elif type == 4: return TagPlaceObject()
|
|
|
+ elif type == 5: return TagRemoveObject()
|
|
|
+ elif type == 6: return TagDefineBits()
|
|
|
+ elif type == 7: return TagDefineButton()
|
|
|
+ elif type == 8: return TagJPEGTables()
|
|
|
+ elif type == 9: return TagSetBackgroundColor()
|
|
|
+ elif type == 10: return TagDefineFont()
|
|
|
+ elif type == 11: return TagDefineText()
|
|
|
+ elif type == 12: return TagDoAction()
|
|
|
+ elif type == 13: return TagDefineFontInfo()
|
|
|
+ elif type == 14: return TagDefineSound()
|
|
|
+ elif type == 15: return TagStartSound()
|
|
|
+ elif type == 17: return TagDefineButtonSound()
|
|
|
+ elif type == 18: return TagSoundStreamHead()
|
|
|
+ elif type == 19: return TagSoundStreamBlock()
|
|
|
+ elif type == 20: return TagDefineBitsLossless()
|
|
|
+ elif type == 21: return TagDefineBitsJPEG2()
|
|
|
+ elif type == 22: return TagDefineShape2()
|
|
|
+ elif type == 24: return TagProtect()
|
|
|
+ elif type == 26: return TagPlaceObject2()
|
|
|
+ elif type == 28: return TagRemoveObject2()
|
|
|
+ elif type == 32: return TagDefineShape3()
|
|
|
+ elif type == 33: return TagDefineText2()
|
|
|
+ elif type == 34: return TagDefineButton2()
|
|
|
+ elif type == 35: return TagDefineBitsJPEG3()
|
|
|
+ elif type == 36: return TagDefineBitsLossless2()
|
|
|
+ elif type == 37: return TagDefineEditText()
|
|
|
+ elif type == 39: return TagDefineSprite()
|
|
|
+ elif type == 41: return TagProductInfo()
|
|
|
+ elif type == 43: return TagFrameLabel()
|
|
|
+ elif type == 45: return TagSoundStreamHead2()
|
|
|
+ elif type == 46: return TagDefineMorphShape()
|
|
|
+ elif type == 48: return TagDefineFont2()
|
|
|
+ elif type == 56: return TagExportAssets()
|
|
|
+ elif type == 58: return TagEnableDebugger()
|
|
|
+ elif type == 59: return TagDoInitAction()
|
|
|
+ elif type == 60: return TagDefineVideoStream()
|
|
|
+ elif type == 61: return TagVideoFrame()
|
|
|
+ elif type == 63: return TagDebugID()
|
|
|
+ elif type == 64: return TagEnableDebugger2()
|
|
|
+ elif type == 65: return TagScriptLimits()
|
|
|
+ elif type == 69: return TagFileAttributes()
|
|
|
+ elif type == 70: return TagPlaceObject3()
|
|
|
+ elif type == 73: return TagDefineFontAlignZones()
|
|
|
+ elif type == 74: return TagCSMTextSettings()
|
|
|
+ elif type == 75: return TagDefineFont3()
|
|
|
+ elif type == 76: return TagSymbolClass()
|
|
|
+ elif type == 77: return TagMetadata()
|
|
|
+ elif type == 78: return TagDefineScalingGrid()
|
|
|
+ elif type == 82: return TagDoABC()
|
|
|
+ elif type == 83: return TagDefineShape4()
|
|
|
+ elif type == 84: return TagDefineMorphShape2()
|
|
|
+ elif type == 86: return TagDefineSceneAndFrameLabelData()
|
|
|
+ elif type == 87: return TagDefineBinaryData()
|
|
|
+ elif type == 88: return TagDefineFontName()
|
|
|
+ elif type == 89: return TagStartSound2()
|
|
|
+ else: return None
|
|
|
+
|
|
|
+class Tag(object):
|
|
|
+ def __init__(self):
|
|
|
+ pass
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ """ The tag name """
|
|
|
+ return ""
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ """ Parses this tag """
|
|
|
+ pass
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ """ Returns the character ids this tag refers to """
|
|
|
+ return set()
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ return "[%02d:%s]" % (self.type, self.name)
|
|
|
+
|
|
|
+class DefinitionTag(Tag):
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(DefinitionTag, self).__init__()
|
|
|
+ self._characterId = -1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def characterId(self):
|
|
|
+ """ Return the character ID """
|
|
|
+ return self._characterId
|
|
|
+
|
|
|
+ @characterId.setter
|
|
|
+ def characterId(self, value):
|
|
|
+ """ Sets the character ID """
|
|
|
+ self._characterId = value
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ pass
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(DefinitionTag, self).get_dependencies()
|
|
|
+ s.add(self.characterId)
|
|
|
+ return s
|
|
|
+
|
|
|
+class DisplayListTag(Tag):
|
|
|
+ characterId = -1
|
|
|
+ def __init__(self):
|
|
|
+ super(DisplayListTag, self).__init__()
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ pass
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(DisplayListTag, self).get_dependencies()
|
|
|
+ s.add(self.characterId)
|
|
|
+ return s
|
|
|
+
|
|
|
+class SWFTimelineContainer(DefinitionTag):
|
|
|
+ def __init__(self):
|
|
|
+ self.tags = []
|
|
|
+ super(SWFTimelineContainer, self).__init__()
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ """ Returns the character ids this tag refers to """
|
|
|
+ s = super(SWFTimelineContainer, self).get_dependencies()
|
|
|
+ for dt in self.all_tags_of_type(DefinitionTag):
|
|
|
+ s.update(dt.get_dependencies())
|
|
|
+ return s
|
|
|
+
|
|
|
+ def parse_tags(self, data, version=1):
|
|
|
+ pos = data.tell()
|
|
|
+ self.file_length = self._get_file_length(data, pos)
|
|
|
+ tag = None
|
|
|
+ while type(tag) != TagEnd:
|
|
|
+ tag = self.parse_tag(data)
|
|
|
+ if tag:
|
|
|
+ #print tag.name
|
|
|
+ self.tags.append(tag)
|
|
|
+
|
|
|
+ def parse_tag(self, data):
|
|
|
+ pos = data.tell()
|
|
|
+ eof = (pos > self.file_length)
|
|
|
+ if eof:
|
|
|
+ #print "WARNING: end of file encountered, no end tag."
|
|
|
+ return TagEnd()
|
|
|
+ raw_tag = data.readraw_tag()
|
|
|
+ tag_type = raw_tag.header.type
|
|
|
+ tag = TagFactory.create(tag_type)
|
|
|
+ if tag is not None:
|
|
|
+ #print tag.name
|
|
|
+ data.seek(raw_tag.pos_content)
|
|
|
+ data.reset_bits_pending()
|
|
|
+ tag.parse(data, raw_tag.header.content_length, tag.version)
|
|
|
+ #except:
|
|
|
+ # print "=> tag_error", tag.name
|
|
|
+ data.seek(pos + raw_tag.header.tag_length)
|
|
|
+ else:
|
|
|
+ #print "[WARNING] unhandled tag %s" % (hex(tag_type))
|
|
|
+ data.skip_bytes(raw_tag.header.tag_length)
|
|
|
+ data.seek(pos + raw_tag.header.tag_length)
|
|
|
+ return tag
|
|
|
+
|
|
|
+ def _get_file_length(self, data, pos):
|
|
|
+ data.f.seek(0, 2)
|
|
|
+ length = data.tell()
|
|
|
+ data.f.seek(pos)
|
|
|
+ return length
|
|
|
+
|
|
|
+ def all_tags_of_type(self, type_or_types, recurse_into_sprites = True):
|
|
|
+ """
|
|
|
+ Generator for all tags of the given type_or_types.
|
|
|
+
|
|
|
+ Generates in breadth-first order, optionally including all sub-containers.
|
|
|
+ """
|
|
|
+ for t in self.tags:
|
|
|
+ if isinstance(t, type_or_types):
|
|
|
+ yield t
|
|
|
+ if recurse_into_sprites:
|
|
|
+ for t in self.tags:
|
|
|
+ # recurse into nested sprites
|
|
|
+ if isinstance(t, SWFTimelineContainer):
|
|
|
+ for containedtag in t.all_tags_of_type(type_or_types):
|
|
|
+ yield containedtag
|
|
|
+
|
|
|
+ def build_dictionary(self):
|
|
|
+ """
|
|
|
+ Return a dictionary of characterIds to their defining tags.
|
|
|
+ """
|
|
|
+ d = {}
|
|
|
+ for t in self.all_tags_of_type(DefinitionTag, recurse_into_sprites = False):
|
|
|
+ if t.characterId in d:
|
|
|
+ #print 'redefinition of characterId %d:' % (t.characterId)
|
|
|
+ #print ' was:', d[t.characterId]
|
|
|
+ #print 'redef:', t
|
|
|
+ raise ValueError('illegal redefinition of character')
|
|
|
+ d[t.characterId] = t
|
|
|
+ return d
|
|
|
+
|
|
|
+ def collect_sound_streams(self):
|
|
|
+ """
|
|
|
+ Return a list of sound streams in this timeline and its children.
|
|
|
+ The streams are returned in order with respect to the timeline.
|
|
|
+
|
|
|
+ A stream is returned as a list: the first element is the tag
|
|
|
+ which introduced that stream; other elements are the tags
|
|
|
+ which made up the stream body (if any).
|
|
|
+ """
|
|
|
+ rc = []
|
|
|
+ current_stream = None
|
|
|
+ # looking in all containers for frames
|
|
|
+ for tag in self.all_tags_of_type((TagSoundStreamHead, TagSoundStreamBlock)):
|
|
|
+ if isinstance(tag, TagSoundStreamHead):
|
|
|
+ # we have a new stream
|
|
|
+ current_stream = [ tag ]
|
|
|
+ rc.append(current_stream)
|
|
|
+ if isinstance(tag, TagSoundStreamBlock):
|
|
|
+ # we have a frame for the current stream
|
|
|
+ current_stream.append(tag)
|
|
|
+ return rc
|
|
|
+
|
|
|
+ def collect_video_streams(self):
|
|
|
+ """
|
|
|
+ Return a list of video streams in this timeline and its children.
|
|
|
+ The streams are returned in order with respect to the timeline.
|
|
|
+
|
|
|
+ A stream is returned as a list: the first element is the tag
|
|
|
+ which introduced that stream; other elements are the tags
|
|
|
+ which made up the stream body (if any).
|
|
|
+ """
|
|
|
+ rc = []
|
|
|
+ streams_by_id = {}
|
|
|
+
|
|
|
+ # scan first for all streams
|
|
|
+ for t in self.all_tags_of_type(TagDefineVideoStream):
|
|
|
+ stream = [ t ]
|
|
|
+ streams_by_id[t.characterId] = stream
|
|
|
+ rc.append(stream)
|
|
|
+
|
|
|
+ # then find the frames
|
|
|
+ for t in self.all_tags_of_type(TagVideoFrame):
|
|
|
+ # we have a frame for the /named/ stream
|
|
|
+ assert t.streamId in streams_by_id
|
|
|
+ streams_by_id[t.streamId].append(t)
|
|
|
+
|
|
|
+ return rc
|
|
|
+
|
|
|
+class TagEnd(Tag):
|
|
|
+ """
|
|
|
+ The End tag marks the end of a file. This must always be the last tag in a file.
|
|
|
+ The End tag is also required to end a sprite definition.
|
|
|
+ The minimum file format version is SWF 1.
|
|
|
+ """
|
|
|
+ TYPE = 0
|
|
|
+ def __init__(self):
|
|
|
+ super(TagEnd, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ """ The tag name """
|
|
|
+ return "End"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagEnd.TYPE
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ return "[%02d:%s]" % (self.type, self.name)
|
|
|
+
|
|
|
+class TagShowFrame(Tag):
|
|
|
+ """
|
|
|
+ The ShowFrame tag instructs Flash Player to display the contents of the
|
|
|
+ display list. The file is paused for the duration of a single frame.
|
|
|
+ The minimum file format version is SWF 1.
|
|
|
+ """
|
|
|
+ TYPE = 1
|
|
|
+ def __init__(self):
|
|
|
+ super(TagShowFrame, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "ShowFrame"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagShowFrame.TYPE
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ return "[%02d:%s]" % (self.type, self.name)
|
|
|
+
|
|
|
+class TagDefineShape(DefinitionTag):
|
|
|
+ """
|
|
|
+ The DefineShape tag defines a shape for later use by control tags such as
|
|
|
+ PlaceObject. The ShapeId uniquely identifies this shape as 'character' in
|
|
|
+ the Dictionary. The ShapeBounds field is the rectangle that completely
|
|
|
+ encloses the shape. The SHAPEWITHSTYLE structure includes all the paths,
|
|
|
+ fill styles and line styles that make up the shape.
|
|
|
+ The minimum file format version is SWF 1.
|
|
|
+ """
|
|
|
+ TYPE = 2
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self._shapes = None
|
|
|
+ self._shape_bounds = None
|
|
|
+ super(TagDefineShape, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineShape"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineShape.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def shapes(self):
|
|
|
+ """ Return SWFShape """
|
|
|
+ return self._shapes
|
|
|
+
|
|
|
+ @property
|
|
|
+ def shape_bounds(self):
|
|
|
+ """ Return the bounds of this tag as a SWFRectangle """
|
|
|
+ return self._shape_bounds
|
|
|
+
|
|
|
+ def export(self, handler=None):
|
|
|
+ """ Export this tag """
|
|
|
+ return self.shapes.export(handler)
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self._shape_bounds = data.readRECT()
|
|
|
+ self._shapes = data.readSHAPEWITHSTYLE(self.level)
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagDefineShape, self).get_dependencies()
|
|
|
+ s.update(self.shapes.get_dependencies())
|
|
|
+ return s
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagDefineShape, self).__str__( ) + " " + \
|
|
|
+ "ID: %d" % self.characterId + ", " + \
|
|
|
+ "Bounds: " + self._shape_bounds.__str__()
|
|
|
+ #s += "\n%s" % self._shapes.__str__()
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagPlaceObject(DisplayListTag):
|
|
|
+ """
|
|
|
+ The PlaceObject tag adds a character to the display list. The CharacterId
|
|
|
+ identifies the character to be added. The Depth field specifies the
|
|
|
+ stacking order of the character. The Matrix field species the position,
|
|
|
+ scale, and rotation of the character. If the size of the PlaceObject tag
|
|
|
+ exceeds the end of the transformation matrix, it is assumed that a
|
|
|
+ ColorTransform field is appended to the record. The ColorTransform field
|
|
|
+ specifies a color effect (such as transparency) that is applied to the character.
|
|
|
+ The same character can be added more than once to the display list with
|
|
|
+ a different depth and transformation matrix.
|
|
|
+ """
|
|
|
+ TYPE = 4
|
|
|
+ hasClipActions = False
|
|
|
+ hasClipDepth = False
|
|
|
+ hasName = False
|
|
|
+ hasRatio = False
|
|
|
+ hasColorTransform = False
|
|
|
+ hasMatrix = False
|
|
|
+ hasCharacter = False
|
|
|
+ hasMove = False
|
|
|
+ hasImage = False
|
|
|
+ hasClassName = False
|
|
|
+ hasCacheAsBitmap = False
|
|
|
+ hasBlendMode = False
|
|
|
+ hasFilterList = False
|
|
|
+ depth = 0
|
|
|
+ matrix = None
|
|
|
+ colorTransform = None
|
|
|
+ # Forward declarations for TagPlaceObject2
|
|
|
+ ratio = 0
|
|
|
+ instanceName = None
|
|
|
+ clipDepth = 0
|
|
|
+ clipActions = None
|
|
|
+ # Forward declarations for TagPlaceObject3
|
|
|
+ className = None
|
|
|
+ blendMode = 0
|
|
|
+ bitmapCache = 0
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self._surfaceFilterList = []
|
|
|
+ super(TagPlaceObject, self).__init__()
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ """ Parses this tag """
|
|
|
+ pos = data.tell()
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.depth = data.readUI16();
|
|
|
+ self.matrix = data.readMATRIX();
|
|
|
+ self.hasCharacter = True;
|
|
|
+ self.hasMatrix = True;
|
|
|
+ if data.tell() - pos < length:
|
|
|
+ colorTransform = data.readCXFORM()
|
|
|
+ self.hasColorTransform = True
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagPlaceObject, self).get_dependencies()
|
|
|
+ if self.hasCharacter:
|
|
|
+ s.add(self.characterId)
|
|
|
+ return s
|
|
|
+
|
|
|
+ @property
|
|
|
+ def filters(self):
|
|
|
+ """ Returns a list of filter """
|
|
|
+ return self._surfaceFilterList
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "PlaceObject"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagPlaceObject.TYPE
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagPlaceObject, self).__str__() + " " + \
|
|
|
+ "Depth: %d, " % self.depth + \
|
|
|
+ "CharacterID: %d" % self.characterId
|
|
|
+ if self.hasName:
|
|
|
+ s+= ", InstanceName: %s" % self.instanceName
|
|
|
+ if self.hasMatrix:
|
|
|
+ s += ", Matrix: %s" % self.matrix.__str__()
|
|
|
+ if self.hasClipDepth:
|
|
|
+ s += ", ClipDepth: %d" % self.clipDepth
|
|
|
+ if self.hasColorTransform:
|
|
|
+ s += ", ColorTransform: %s" % self.colorTransform.__str__()
|
|
|
+ if self.hasFilterList:
|
|
|
+ s += ", Filters: %d" % len(self.filters)
|
|
|
+ if self.hasBlendMode:
|
|
|
+ s += ", Blendmode: %d" % self.blendMode
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagRemoveObject(DisplayListTag):
|
|
|
+ """
|
|
|
+ The RemoveObject tag removes the specified character (at the specified depth)
|
|
|
+ from the display list.
|
|
|
+ The minimum file format version is SWF 1.
|
|
|
+ """
|
|
|
+ TYPE = 5
|
|
|
+ depth = 0
|
|
|
+ def __init__(self):
|
|
|
+ super(TagRemoveObject, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "RemoveObject"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagRemoveObject.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ """ Parses this tag """
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.depth = data.readUI16()
|
|
|
+
|
|
|
+class TagDefineBits(DefinitionTag):
|
|
|
+ """
|
|
|
+ This tag defines a bitmap character with JPEG compression. It contains only
|
|
|
+ the JPEG compressed image data (from the Frame Header onward). A separate
|
|
|
+ JPEGTables tag contains the JPEG encoding data used to encode this image
|
|
|
+ (the Tables/Misc segment).
|
|
|
+ NOTE:
|
|
|
+ Only one JPEGTables tag is allowed in a SWF file, and thus all bitmaps
|
|
|
+ defined with DefineBits must share common encoding tables.
|
|
|
+ The data in this tag begins with the JPEG SOI marker 0xFF, 0xD8 and ends
|
|
|
+ with the EOI marker 0xFF, 0xD9. Before version 8 of the SWF file format,
|
|
|
+ SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8 before
|
|
|
+ the JPEG SOI marker.
|
|
|
+ """
|
|
|
+ TYPE = 6
|
|
|
+ bitmapData = None
|
|
|
+ def __init__(self):
|
|
|
+ self.bitmapData = BytesIO()
|
|
|
+ self.bitmapType = BitmapType.JPEG
|
|
|
+ super(TagDefineBits, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineBits"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineBits.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.bitmapData = BytesIO()
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ if length > 2:
|
|
|
+ self.bitmapData.write(data.f.read(length - 2))
|
|
|
+ self.bitmapData.seek(0)
|
|
|
+
|
|
|
+class TagJPEGTables(DefinitionTag):
|
|
|
+ """
|
|
|
+ This tag defines the JPEG encoding table (the Tables/Misc segment) for all
|
|
|
+ JPEG images defined using the DefineBits tag. There may only be one
|
|
|
+ JPEGTables tag in a SWF file.
|
|
|
+ The data in this tag begins with the JPEG SOI marker 0xFF, 0xD8 and ends
|
|
|
+ with the EOI marker 0xFF, 0xD9. Before version 8 of the SWF file format,
|
|
|
+ SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8 before
|
|
|
+ the JPEG SOI marker.
|
|
|
+ The minimum file format version for this tag is SWF 1.
|
|
|
+ """
|
|
|
+ TYPE = 8
|
|
|
+ jpegTables = None
|
|
|
+ length = 0
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagJPEGTables, self).__init__()
|
|
|
+ self.jpegTables = BytesIO()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "JPEGTables"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagJPEGTables.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.length = length
|
|
|
+ if length > 0:
|
|
|
+ self.jpegTables.write(data.f.read(length))
|
|
|
+ self.jpegTables.seek(0)
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagJPEGTables, self).__str__()
|
|
|
+ s += " Length: %d" % self.length
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagSetBackgroundColor(Tag):
|
|
|
+ """
|
|
|
+ The SetBackgroundColor tag sets the background color of the display.
|
|
|
+ The minimum file format version is SWF 1.
|
|
|
+ """
|
|
|
+ TYPE = 9
|
|
|
+ color = 0
|
|
|
+ def __init__(self):
|
|
|
+ super(TagSetBackgroundColor, self).__init__()
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.color = data.readRGB()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "SetBackgroundColor"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagSetBackgroundColor.TYPE
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagSetBackgroundColor, self).__str__()
|
|
|
+ s += " Color: " + ColorUtils.to_rgb_string(self.color)
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagDefineFont(DefinitionTag):
|
|
|
+ """
|
|
|
+ The DefineFont tag defines the shape outlines of each glyph used in a
|
|
|
+ particular font. Only the glyphs that are used by subsequent DefineText
|
|
|
+ tags are actually defined.
|
|
|
+ DefineFont tags cannot be used for dynamic text. Dynamic text requires
|
|
|
+ the DefineFont2 tag.
|
|
|
+ The minimum file format version is SWF 1.
|
|
|
+ """
|
|
|
+ TYPE= 10
|
|
|
+ offsetTable = []
|
|
|
+ glyphShapeTable = []
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineFont, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineFont"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineFont.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def unitDivisor(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.glyphShapeTable = []
|
|
|
+ self.offsetTable = []
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+
|
|
|
+ # Because the glyph shape table immediately follows the offset table,
|
|
|
+ # the number of entries in each table (the number of glyphs in the
|
|
|
+ # font) can be inferred by dividing the first entry in the offset
|
|
|
+ # table by two.
|
|
|
+ self.offsetTable.append(data.readUI16())
|
|
|
+ numGlyphs = self.offsetTable[0] / 2
|
|
|
+
|
|
|
+ for i in range(1, numGlyphs):
|
|
|
+ self.offsetTable.append(data.readUI16())
|
|
|
+
|
|
|
+ for i in range(numGlyphs):
|
|
|
+ self.glyphShapeTable.append(data.readSHAPE(self.unitDivisor))
|
|
|
+
|
|
|
+class TagDefineText(DefinitionTag):
|
|
|
+ """
|
|
|
+ The DefineText tag defines a block of static text. It describes the font,
|
|
|
+ size, color, and exact position of every character in the text object.
|
|
|
+ The minimum file format version is SWF 1.
|
|
|
+ """
|
|
|
+ TYPE = 11
|
|
|
+ textBounds = None
|
|
|
+ textMatrix = None
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ self._records = []
|
|
|
+ super(TagDefineText, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDefineText"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineText.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagDefineText, self).get_dependencies()
|
|
|
+ for r in self.records:
|
|
|
+ s.update(r.get_dependencies())
|
|
|
+ return s
|
|
|
+
|
|
|
+ @property
|
|
|
+ def records(self):
|
|
|
+ """ Return list of SWFTextRecord """
|
|
|
+ return self._records
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self._records = []
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.textBounds = data.readRECT()
|
|
|
+ self.textMatrix = data.readMATRIX()
|
|
|
+ glyphBits = data.readUI8()
|
|
|
+ advanceBits = data.readUI8()
|
|
|
+ record = None
|
|
|
+ record = data.readTEXTRECORD(glyphBits, advanceBits, record, self.level)
|
|
|
+ while not record is None:
|
|
|
+ self._records.append(record)
|
|
|
+ record = data.readTEXTRECORD(glyphBits, advanceBits, record, self.level)
|
|
|
+
|
|
|
+class TagDoAction(Tag):
|
|
|
+ """
|
|
|
+ DoAction instructs Flash Player to perform a list of actions when the
|
|
|
+ current frame is complete. The actions are performed when the ShowFrame
|
|
|
+ tag is encountered, regardless of where in the frame the DoAction tag appears.
|
|
|
+ Starting with SWF 9, if the ActionScript3 field of the FileAttributes tag is 1,
|
|
|
+ the contents of the DoAction tag will be ignored.
|
|
|
+ """
|
|
|
+ TYPE = 12
|
|
|
+ def __init__(self):
|
|
|
+ self._actions = []
|
|
|
+ super(TagDoAction, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DoAction"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ """ Return the SWF tag type """
|
|
|
+ return TagDoAction.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ """ Return the minimum SWF version """
|
|
|
+ return 3
|
|
|
+
|
|
|
+ @property
|
|
|
+ def actions(self):
|
|
|
+ """ Return list of SWFActionRecord """
|
|
|
+ return self._actions
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self._actions = data.readACTIONRECORDs()
|
|
|
+
|
|
|
+class TagDefineFontInfo(Tag):
|
|
|
+ """
|
|
|
+ The DefineFontInfo tag defines a mapping from a glyph font (defined with DefineFont) to a
|
|
|
+ device font. It provides a font name and style to pass to the playback platform's text engine,
|
|
|
+ and a table of character codes that identifies the character represented by each glyph in the
|
|
|
+ corresponding DefineFont tag, allowing the glyph indices of a DefineText tag to be converted
|
|
|
+ to character strings.
|
|
|
+ The presence of a DefineFontInfo tag does not force a glyph font to become a device font; it
|
|
|
+ merely makes the option available. The actual choice between glyph and device usage is made
|
|
|
+ according to the value of devicefont (see the introduction) or the value of UseOutlines in a
|
|
|
+ DefineEditText tag. If a device font is unavailable on a playback platform, Flash Player will
|
|
|
+ fall back to glyph text.
|
|
|
+ """
|
|
|
+ TYPE = 13
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineFontInfo, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineFontInfo"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineFontInfo.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def unitDivisor(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagDefineFontInfo, self).get_dependencies()
|
|
|
+ s.add(self.characterId)
|
|
|
+ return s
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.codeTable = []
|
|
|
+
|
|
|
+ # FontID
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+
|
|
|
+ fontNameLen = data.readUI8()
|
|
|
+
|
|
|
+ self.fontName = ""
|
|
|
+ self.useGlyphText = False
|
|
|
+
|
|
|
+ # Read in font name, one character at a time. If any of the
|
|
|
+ # characters are non-ASCII, assume that glyph text should be
|
|
|
+ # used rather than device text.
|
|
|
+ for i in range(fontNameLen):
|
|
|
+ ord = data.readUI8()
|
|
|
+
|
|
|
+ if ord in range(128):
|
|
|
+ self.fontName += chr(ord)
|
|
|
+ else:
|
|
|
+ self.useGlyphText = True
|
|
|
+
|
|
|
+ if self.useGlyphText:
|
|
|
+ self.fontName = "Font_{0}".format(self.characterId)
|
|
|
+
|
|
|
+ flags = data.readUI8()
|
|
|
+
|
|
|
+ self.smallText = ((flags & 0x20) != 0)
|
|
|
+ self.shiftJIS = ((flags & 0x10) != 0)
|
|
|
+ self.ansi = ((flags & 0x08) != 0)
|
|
|
+ self.italic = ((flags & 0x04) != 0)
|
|
|
+ self.bold = ((flags & 0x02) != 0)
|
|
|
+ self.wideCodes = ((flags & 0x01) != 0)
|
|
|
+
|
|
|
+ if self.wideCodes:
|
|
|
+ numGlyphs = (length - 2 - 1 - fontNameLen - 1) / 2
|
|
|
+ else:
|
|
|
+ numGlyphs = length - 2 - 1 - fontNameLen - 1
|
|
|
+
|
|
|
+ for i in range(0, numGlyphs):
|
|
|
+ self.codeTable.append(data.readUI16() if self.wideCodes else data.readUI8())
|
|
|
+
|
|
|
+class TagDefineBitsLossless(DefinitionTag):
|
|
|
+ """
|
|
|
+ Defines a lossless bitmap character that contains RGB bitmap data compressed
|
|
|
+ with ZLIB. The data format used by the ZLIB library is described by
|
|
|
+ Request for Comments (RFCs) documents 1950 to 1952.
|
|
|
+ Two kinds of bitmaps are supported. Colormapped images define a colormap of
|
|
|
+ up to 256 colors, each represented by a 24-bit RGB value, and then use
|
|
|
+ 8-bit pixel values to index into the colormap. Direct images store actual
|
|
|
+ pixel color values using 15 bits (32,768 colors) or 24 bits (about 17 million colors).
|
|
|
+ The minimum file format version for this tag is SWF 2.
|
|
|
+ """
|
|
|
+ TYPE = 20
|
|
|
+ bitmapData = None
|
|
|
+ image_buffer = ""
|
|
|
+ bitmap_format = 0
|
|
|
+ bitmap_width = 0
|
|
|
+ bitmap_height = 0
|
|
|
+ bitmap_color_size = 0
|
|
|
+ zlib_bitmap_data = None
|
|
|
+ padded_width = 0
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineBitsLossless, self).__init__()
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ import zlib
|
|
|
+ self.image_buffer = ""
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.bitmap_format = data.readUI8()
|
|
|
+ self.bitmap_width = data.readUI16()
|
|
|
+ self.bitmap_height = data.readUI16()
|
|
|
+ if self.bitmap_format == BitmapFormat.BIT_8:
|
|
|
+ self.bitmap_color_size = data.readUI8()
|
|
|
+ self.zlib_bitmap_data = data.f.read(length-8)
|
|
|
+ else:
|
|
|
+ self.zlib_bitmap_data = data.f.read(length-7)
|
|
|
+
|
|
|
+ # decompress zlib encoded bytes
|
|
|
+ compressed_length = len(self.zlib_bitmap_data)
|
|
|
+ zip = zlib.decompressobj()
|
|
|
+ temp = BytesIO()
|
|
|
+ temp.write(zip.decompress(self.zlib_bitmap_data))
|
|
|
+ temp.seek(0, 2)
|
|
|
+ uncompressed_length = temp.tell()
|
|
|
+ temp.seek(0)
|
|
|
+
|
|
|
+ # padding : should be aligned to 32 bit boundary
|
|
|
+ self.padded_width = self.bitmap_width
|
|
|
+ while self.padded_width % 4 != 0:
|
|
|
+ self.padded_width += 1
|
|
|
+ t = self.padded_width * self.bitmap_height
|
|
|
+
|
|
|
+ is_lossless2 = (type(self) == TagDefineBitsLossless2)
|
|
|
+ im = None
|
|
|
+ self.bitmapData = BytesIO()
|
|
|
+
|
|
|
+ indexed_colors = []
|
|
|
+ if self.bitmap_format == BitmapFormat.BIT_8:
|
|
|
+ for i in range(0, self.bitmap_color_size + 1):
|
|
|
+ r = ord(temp.read(1))
|
|
|
+ g = ord(temp.read(1))
|
|
|
+ b = ord(temp.read(1))
|
|
|
+ a = ord(temp.read(1)) if is_lossless2 else 0xff
|
|
|
+ indexed_colors.append(struct.pack("BBBB", r, g, b, a))
|
|
|
+
|
|
|
+ # create the image buffer
|
|
|
+ s = BytesIO()
|
|
|
+ for i in range(t):
|
|
|
+ a = ord(temp.read(1))
|
|
|
+ s.write(indexed_colors[a%len(indexed_colors)])
|
|
|
+ self.image_buffer = s.getvalue()
|
|
|
+ s.close()
|
|
|
+
|
|
|
+ im = Image.frombytes("RGBA", (self.padded_width, self.bitmap_height), self.image_buffer)
|
|
|
+ im = im.crop((0, 0, self.bitmap_width, self.bitmap_height))
|
|
|
+
|
|
|
+ elif self.bitmap_format == BitmapFormat.BIT_15:
|
|
|
+ raise Exception("DefineBitsLossless: BIT_15 not yet implemented")
|
|
|
+ elif self.bitmap_format == BitmapFormat.BIT_24:
|
|
|
+ # we have no padding, since PIX24s are 32-bit aligned
|
|
|
+ t = self.bitmap_width * self.bitmap_height
|
|
|
+ # read PIX24's
|
|
|
+ s = BytesIO()
|
|
|
+ for i in range(0, t):
|
|
|
+ if not is_lossless2:
|
|
|
+ temp.read(1) # reserved, always 0
|
|
|
+ a = ord(temp.read(1)) if is_lossless2 else 0xff
|
|
|
+ r = ord(temp.read(1))
|
|
|
+ g = ord(temp.read(1))
|
|
|
+ b = ord(temp.read(1))
|
|
|
+ s.write(struct.pack("BBBB", r, g, b, a))
|
|
|
+ self.image_buffer = s.getvalue()
|
|
|
+ im = Image.frombytes("RGBA", (self.bitmap_width, self.bitmap_height), self.image_buffer)
|
|
|
+ else:
|
|
|
+ raise Exception("unhandled bitmap format! %s %d" % (BitmapFormat.tostring(self.bitmap_format), self.bitmap_format))
|
|
|
+
|
|
|
+ if not im is None:
|
|
|
+ im.save(self.bitmapData, "PNG")
|
|
|
+ self.bitmapData.seek(0)
|
|
|
+ self.bitmapType = ImageUtils.get_image_type(self.bitmapData)
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineBitsLossless"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineBitsLossless.TYPE
|
|
|
+
|
|
|
+class TagDefineBitsJPEG2(TagDefineBits):
|
|
|
+ """
|
|
|
+ This tag defines a bitmap character with JPEG compression. It differs from
|
|
|
+ DefineBits in that it contains both the JPEG encoding table and the JPEG
|
|
|
+ image data. This tag allows multiple JPEG images with differing encoding
|
|
|
+ tables to be defined within a single SWF file.
|
|
|
+ The data in this tag begins with the JPEG SOI marker 0xFF, 0xD8 and ends
|
|
|
+ with the EOI marker 0xFF, 0xD9. Before version 8 of the SWF file format,
|
|
|
+ SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8
|
|
|
+ before the JPEG SOI marker.
|
|
|
+ In addition to specifying JPEG data, DefineBitsJPEG2 can also contain PNG
|
|
|
+ image data and non-animated GIF89a image data.
|
|
|
+
|
|
|
+ - If ImageData begins with the eight bytes 0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A,
|
|
|
+ the ImageData contains PNG data.
|
|
|
+ - If ImageData begins with the six bytes 0x47 0x49 0x46 0x38 0x39 0x61, the ImageData
|
|
|
+ contains GIF89a data.
|
|
|
+
|
|
|
+ The minimum file format version for this tag is SWF 2. The minimum file format
|
|
|
+ version for embedding PNG of GIF89a data is SWF 8.
|
|
|
+ """
|
|
|
+ TYPE = 21
|
|
|
+ bitmapType = 0
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineBitsJPEG2, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineBitsJPEG2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineBitsJPEG2.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 2 if self.bitmapType == BitmapType.JPEG else 8
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 2
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ super(TagDefineBitsJPEG2, self).parse(data, length, version)
|
|
|
+ self.bitmapType = ImageUtils.get_image_type(self.bitmapData)
|
|
|
+
|
|
|
+class TagDefineShape2(TagDefineShape):
|
|
|
+ """
|
|
|
+ DefineShape2 extends the capabilities of DefineShape with the ability
|
|
|
+ to support more than 255 styles in the style list and multiple style
|
|
|
+ lists in a single shape.
|
|
|
+ The minimum file format version is SWF 2.
|
|
|
+ """
|
|
|
+ TYPE = 22
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineShape2, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineShape2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineShape2.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 2
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 2
|
|
|
+
|
|
|
+class TagPlaceObject2(TagPlaceObject):
|
|
|
+ """
|
|
|
+ The PlaceObject2 tag extends the functionality of the PlaceObject tag.
|
|
|
+ The PlaceObject2 tag can both add a character to the display list, and
|
|
|
+ modify the attributes of a character that is already on the display list.
|
|
|
+ The PlaceObject2 tag changed slightly from SWF 4 to SWF 5. In SWF 5,
|
|
|
+ clip actions were added.
|
|
|
+ The tag begins with a group of flags that indicate which fields are
|
|
|
+ present in the tag. The optional fields are CharacterId, Matrix,
|
|
|
+ ColorTransform, Ratio, ClipDepth, Name, and ClipActions.
|
|
|
+ The Depth field is the only field that is always required.
|
|
|
+ The depth value determines the stacking order of the character.
|
|
|
+ Characters with lower depth values are displayed underneath characters
|
|
|
+ with higher depth values. A depth value of 1 means the character is
|
|
|
+ displayed at the bottom of the stack. Any given depth can have only one
|
|
|
+ character. This means a character that is already on the display list can
|
|
|
+ be identified by its depth alone (that is, a CharacterId is not required).
|
|
|
+ The PlaceFlagMove and PlaceFlagHasCharacter tags indicate whether a new
|
|
|
+ character is being added to the display list, or a character already on the
|
|
|
+ display list is being modified. The meaning of the flags is as follows:
|
|
|
+
|
|
|
+ - PlaceFlagMove = 0 and PlaceFlagHasCharacter = 1 A new character
|
|
|
+ (with ID of CharacterId) is placed on the display list at the specified
|
|
|
+ depth. Other fields set the attributes of this new character.
|
|
|
+ - PlaceFlagMove = 1 and PlaceFlagHasCharacter = 0
|
|
|
+ The character at the specified depth is modified. Other fields modify the
|
|
|
+ attributes of this character. Because any given depth can have only one
|
|
|
+ character, no CharacterId is required.
|
|
|
+ - PlaceFlagMove = 1 and PlaceFlagHasCharacter = 1
|
|
|
+ The character at the specified Depth is removed, and a new character
|
|
|
+ (with ID of CharacterId) is placed at that depth. Other fields set the
|
|
|
+ attributes of this new character.
|
|
|
+ For example, a character that is moved over a series of frames has
|
|
|
+ PlaceFlagHasCharacter set in the first frame, and PlaceFlagMove set in
|
|
|
+ subsequent frames. The first frame places the new character at the desired
|
|
|
+ depth, and sets the initial transformation matrix. Subsequent frames replace
|
|
|
+ the transformation matrix of the character at the desired depth.
|
|
|
+
|
|
|
+ The optional fields in PlaceObject2 have the following meaning:
|
|
|
+ - The CharacterId field specifies the character to be added to the display list.
|
|
|
+ CharacterId is used only when a new character is being added. If a character
|
|
|
+ that is already on the display list is being modified, the CharacterId field is absent.
|
|
|
+ - The Matrix field specifies the position, scale and rotation of the character
|
|
|
+ being added or modified.
|
|
|
+ - The ColorTransform field specifies the color effect applied to the character
|
|
|
+ being added or modified.
|
|
|
+ - The Ratio field specifies a morph ratio for the character being added or modified.
|
|
|
+ This field applies only to characters defined with DefineMorphShape, and controls
|
|
|
+ how far the morph has progressed. A ratio of zero displays the character at the start
|
|
|
+ of the morph. A ratio of 65535 displays the character at the end of the morph.
|
|
|
+ For values between zero and 65535 Flash Player interpolates between the start and end
|
|
|
+ shapes, and displays an in- between shape.
|
|
|
+ - The ClipDepth field specifies the top-most depth that will be masked by the character
|
|
|
+ being added. A ClipDepth of zero indicates that this is not a clipping character.
|
|
|
+ - The Name field specifies a name for the character being added or modified. This field
|
|
|
+ is typically used with sprite characters, and is used to identify the sprite for
|
|
|
+ SetTarget actions. It allows the main file (or other sprites) to perform actions
|
|
|
+ inside the sprite (see 'Sprites and Movie Clips' on page 231).
|
|
|
+ - The ClipActions field, which is valid only for placing sprite characters, defines
|
|
|
+ one or more event handlers to be invoked when certain events occur.
|
|
|
+ """
|
|
|
+ TYPE = 26
|
|
|
+ def __init__(self):
|
|
|
+ super(TagPlaceObject2, self).__init__()
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ flags = data.readUI8()
|
|
|
+ self.hasClipActions = (flags & 0x80) != 0
|
|
|
+ self.hasClipDepth = (flags & 0x40) != 0
|
|
|
+ self.hasName = (flags & 0x20) != 0
|
|
|
+ self.hasRatio = (flags & 0x10) != 0
|
|
|
+ self.hasColorTransform = (flags & 0x08) != 0
|
|
|
+ self.hasMatrix = (flags & 0x04) != 0
|
|
|
+ self.hasCharacter = (flags & 0x02) != 0
|
|
|
+ self.hasMove = (flags & 0x01) != 0
|
|
|
+ self.depth = data.readUI16()
|
|
|
+ if self.hasCharacter:
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ if self.hasMatrix:
|
|
|
+ self.matrix = data.readMATRIX()
|
|
|
+ if self.hasColorTransform:
|
|
|
+ self.colorTransform = data.readCXFORMWITHALPHA()
|
|
|
+ if self.hasRatio:
|
|
|
+ self.ratio = data.readUI16()
|
|
|
+ if self.hasName:
|
|
|
+ self.instanceName = data.readString()
|
|
|
+ if self.hasClipDepth:
|
|
|
+ self.clipDepth = data.readUI16()
|
|
|
+ if self.hasClipActions:
|
|
|
+ self.clipActions = data.readCLIPACTIONS(version);
|
|
|
+ #raise Exception("PlaceObject2: ClipActions not yet implemented!")
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "PlaceObject2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagPlaceObject2.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 2
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 3
|
|
|
+
|
|
|
+class TagRemoveObject2(TagRemoveObject):
|
|
|
+ """
|
|
|
+ The RemoveObject2 tag removes the character at the specified depth
|
|
|
+ from the display list.
|
|
|
+ The minimum file format version is SWF 3.
|
|
|
+ """
|
|
|
+ TYPE = 28
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagRemoveObject2, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "RemoveObject2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagRemoveObject2.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 2
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 3
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.depth = data.readUI16()
|
|
|
+
|
|
|
+class TagDefineShape3(TagDefineShape2):
|
|
|
+ """
|
|
|
+ DefineShape3 extends the capabilities of DefineShape2 by extending
|
|
|
+ all of the RGB color fields to support RGBA with opacity information.
|
|
|
+ The minimum file format version is SWF 3.
|
|
|
+ """
|
|
|
+ TYPE = 32
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineShape3, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineShape3"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineShape3.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 3
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 3
|
|
|
+
|
|
|
+class TagDefineText2(TagDefineText):
|
|
|
+ """
|
|
|
+ The DefineText tag defines a block of static text. It describes the font,
|
|
|
+ size, color, and exact position of every character in the text object.
|
|
|
+ The minimum file format version is SWF 3.
|
|
|
+ """
|
|
|
+ TYPE = 33
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineText2, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineText2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineText2.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 2
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 3
|
|
|
+
|
|
|
+class TagDefineBitsJPEG3(TagDefineBitsJPEG2):
|
|
|
+ """
|
|
|
+ This tag defines a bitmap character with JPEG compression. This tag
|
|
|
+ extends DefineBitsJPEG2, adding alpha channel (opacity) data.
|
|
|
+ Opacity/transparency information is not a standard feature in JPEG images,
|
|
|
+ so the alpha channel information is encoded separately from the JPEG data,
|
|
|
+ and compressed using the ZLIB standard for compression. The data format
|
|
|
+ used by the ZLIB library is described by Request for Comments (RFCs)
|
|
|
+ documents 1950 to 1952.
|
|
|
+ The data in this tag begins with the JPEG SOI marker 0xFF, 0xD8 and ends
|
|
|
+ with the EOI marker 0xFF, 0xD9. Before version 8 of the SWF file format,
|
|
|
+ SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8
|
|
|
+ before the JPEG SOI marker.
|
|
|
+ In addition to specifying JPEG data, DefineBitsJPEG2 can also contain
|
|
|
+ PNG image data and non-animated GIF89a image data.
|
|
|
+ - If ImageData begins with the eight bytes 0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A,
|
|
|
+ the ImageData contains PNG data.
|
|
|
+ - If ImageData begins with the six bytes 0x47 0x49 0x46 0x38 0x39 0x61,
|
|
|
+ the ImageData contains GIF89a data.
|
|
|
+ If ImageData contains PNG or GIF89a data, the optional BitmapAlphaData is
|
|
|
+ not supported.
|
|
|
+ The minimum file format version for this tag is SWF 3. The minimum file
|
|
|
+ format version for embedding PNG of GIF89a data is SWF 8.
|
|
|
+ """
|
|
|
+ TYPE = 35
|
|
|
+ def __init__(self):
|
|
|
+ self.bitmapAlphaData = BytesIO()
|
|
|
+ super(TagDefineBitsJPEG3, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineBitsJPEG3"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineBitsJPEG3.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 3 if self.bitmapType == BitmapType.JPEG else 8
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 3
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ import zlib
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ alphaOffset = data.readUI32()
|
|
|
+ self.bitmapAlphaData = BytesIO()
|
|
|
+ self.bitmapData = BytesIO()
|
|
|
+ self.bitmapData.write(data.f.read(alphaOffset))
|
|
|
+ self.bitmapData.seek(0)
|
|
|
+ self.bitmapType = ImageUtils.get_image_type(self.bitmapData)
|
|
|
+ alphaDataSize = length - alphaOffset - 6
|
|
|
+ if alphaDataSize > 0:
|
|
|
+ self.bitmapAlphaData.write(data.f.read(alphaDataSize))
|
|
|
+ self.bitmapAlphaData.seek(0)
|
|
|
+ # decompress zlib encoded bytes
|
|
|
+ zip = zlib.decompressobj()
|
|
|
+ temp = BytesIO()
|
|
|
+ temp.write(zip.decompress(self.bitmapAlphaData.read()))
|
|
|
+ temp.seek(0)
|
|
|
+ self.bitmapAlphaData = temp
|
|
|
+
|
|
|
+class TagDefineBitsLossless2(TagDefineBitsLossless):
|
|
|
+ """
|
|
|
+ DefineBitsLossless2 extends DefineBitsLossless with support for
|
|
|
+ opacity (alpha values). The colormap colors in colormapped images
|
|
|
+ are defined using RGBA values, and direct images store 32-bit
|
|
|
+ ARGB colors for each pixel. The intermediate 15-bit color depth
|
|
|
+ is not available in DefineBitsLossless2.
|
|
|
+ The minimum file format version for this tag is SWF 3.
|
|
|
+ """
|
|
|
+ TYPE = 36
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineBitsLossless2, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineBitsLossless2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineBitsLossless2.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 2
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 3
|
|
|
+
|
|
|
+class TagDefineSprite(SWFTimelineContainer):
|
|
|
+ """
|
|
|
+ The DefineSprite tag defines a sprite character. It consists of
|
|
|
+ a character ID and a frame count, followed by a series of control
|
|
|
+ tags. The sprite is terminated with an End tag.
|
|
|
+ The length specified in the Header reflects the length of the
|
|
|
+ entire DefineSprite tag, including the ControlTags field.
|
|
|
+ Definition tags (such as DefineShape) are not allowed in the
|
|
|
+ DefineSprite tag. All of the characters that control tags refer to
|
|
|
+ in the sprite must be defined in the main body of the file before
|
|
|
+ the sprite is defined.
|
|
|
+ The minimum file format version is SWF 3.
|
|
|
+ """
|
|
|
+ TYPE = 39
|
|
|
+ frameCount = 0
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineSprite, self).__init__()
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.frameCount = data.readUI16()
|
|
|
+ self.parse_tags(data, version)
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagDefineSprite, self).get_dependencies()
|
|
|
+ s.add(self.characterId)
|
|
|
+ return s
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineSprite"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineSprite.TYPE
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagDefineSprite, self).__str__() + " " + \
|
|
|
+ "ID: %d" % self.characterId
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagFrameLabel(Tag):
|
|
|
+ """
|
|
|
+ The FrameLabel tag gives the specified Name to the current frame.
|
|
|
+ ActionGoToLabel uses this name to identify the frame.
|
|
|
+ The minimum file format version is SWF 3.
|
|
|
+ """
|
|
|
+ TYPE = 43
|
|
|
+ frameName = ""
|
|
|
+ namedAnchorFlag = False
|
|
|
+ def __init__(self):
|
|
|
+ super(TagFrameLabel, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "FrameLabel"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagFrameLabel.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 3
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ start = data.tell()
|
|
|
+ self.frameName = data.readString()
|
|
|
+ if (data.tell() - start) < length:
|
|
|
+ data.readUI8() # Named anchor flag, always 1
|
|
|
+ self.namedAnchorFlag = True
|
|
|
+
|
|
|
+class TagDefineMorphShape(DefinitionTag):
|
|
|
+ """
|
|
|
+ The DefineMorphShape tag defines the start and end states of a morph
|
|
|
+ sequence. A morph object should be displayed with the PlaceObject2 tag,
|
|
|
+ where the ratio field specifies how far the morph has progressed.
|
|
|
+ The minimum file format version is SWF 3.
|
|
|
+ """
|
|
|
+ TYPE = 46
|
|
|
+ def __init__(self):
|
|
|
+ self._morphFillStyles = []
|
|
|
+ self._morphLineStyles = []
|
|
|
+ super(TagDefineMorphShape, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineMorphShape"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineMorphShape.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 3
|
|
|
+
|
|
|
+ @property
|
|
|
+ def morph_fill_styles(self):
|
|
|
+ """ Return list of SWFMorphFillStyle """
|
|
|
+ return self._morphFillStyles
|
|
|
+
|
|
|
+ @property
|
|
|
+ def morph_line_styles(self):
|
|
|
+ """ Return list of SWFMorphLineStyle """
|
|
|
+ return self._morphLineStyles
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self._morphFillStyles = []
|
|
|
+ self._morphLineStyles = []
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.startBounds = data.readRECT()
|
|
|
+ self.endBounds = data.readRECT()
|
|
|
+ offset = data.readUI32()
|
|
|
+
|
|
|
+ self._morphFillStyles = data.readMORPHFILLSTYLEARRAY()
|
|
|
+ self._morphLineStyles = data.readMORPHLINESTYLEARRAY(version = 1)
|
|
|
+ self.startEdges = data.readSHAPE();
|
|
|
+ self.endEdges = data.readSHAPE();
|
|
|
+
|
|
|
+class TagDefineFont2(TagDefineFont):
|
|
|
+ TYPE= 48
|
|
|
+ def __init__(self):
|
|
|
+ self.glyphShapeTable = []
|
|
|
+ super(TagDefineFont2, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineFont2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineFont2.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 2
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 3
|
|
|
+
|
|
|
+ @property
|
|
|
+ def unitDivisor(self):
|
|
|
+ return 20
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.glyphShapeTable = []
|
|
|
+ self.codeTable = []
|
|
|
+ self.fontAdvanceTable = []
|
|
|
+ self.fontBoundsTable = []
|
|
|
+ self.fontKerningTable = []
|
|
|
+
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+
|
|
|
+ flags = data.readUI8()
|
|
|
+
|
|
|
+ self.hasLayout = ((flags & 0x80) != 0)
|
|
|
+ self.shiftJIS = ((flags & 0x40) != 0)
|
|
|
+ self.smallText = ((flags & 0x20) != 0)
|
|
|
+ self.ansi = ((flags & 0x10) != 0)
|
|
|
+ self.wideOffsets = ((flags & 0x08) != 0)
|
|
|
+ self.wideCodes = ((flags & 0x04) != 0)
|
|
|
+ self.italic = ((flags & 0x02) != 0)
|
|
|
+ self.bold = ((flags & 0x01) != 0)
|
|
|
+ self.languageCode = data.readLANGCODE()
|
|
|
+
|
|
|
+ fontNameLen = data.readUI8()
|
|
|
+ fontNameRaw = BytesIO()
|
|
|
+ fontNameRaw.write(data.f.read(fontNameLen))
|
|
|
+ fontNameRaw.seek(0)
|
|
|
+ self.fontName = fontNameRaw.read()
|
|
|
+
|
|
|
+ numGlyphs = data.readUI16()
|
|
|
+ numSkip = 2 if self.wideOffsets else 1
|
|
|
+ # don't # Skip offsets. We don't need them.
|
|
|
+ # Adobe Flash Player works in this way
|
|
|
+
|
|
|
+ startOfOffsetTable = data.f.tell()
|
|
|
+ offsetTable = []
|
|
|
+ for i in range(0, numGlyphs):
|
|
|
+ offsetTable.append(data.readUI32() if self.wideOffsets else data.readUI16())
|
|
|
+
|
|
|
+ codeTableOffset = data.readUI32() if self.wideOffsets else data.readUI16()
|
|
|
+ for i in range(0, numGlyphs):
|
|
|
+ data.f.seek(startOfOffsetTable + offsetTable[i])
|
|
|
+ self.glyphShapeTable.append(data.readSHAPE(self.unitDivisor))
|
|
|
+ data.f.seek(startOfOffsetTable + codeTableOffset)
|
|
|
+ for i in range(0, numGlyphs):
|
|
|
+ self.codeTable.append(data.readUI16() if self.wideCodes else data.readUI8())
|
|
|
+
|
|
|
+ if self.hasLayout:
|
|
|
+ self.ascent = data.readSI16()
|
|
|
+ self.descent = data.readSI16()
|
|
|
+ self.leading = data.readSI16()
|
|
|
+ for i in range(0, numGlyphs):
|
|
|
+ self.fontAdvanceTable.append(data.readSI16())
|
|
|
+ for i in range(0, numGlyphs):
|
|
|
+ self.fontBoundsTable.append(data.readRECT())
|
|
|
+ kerningCount = data.readUI16()
|
|
|
+ for i in range(0, kerningCount):
|
|
|
+ self.fontKerningTable.append(data.readKERNINGRECORD(self.wideCodes))
|
|
|
+
|
|
|
+class TagFileAttributes(Tag):
|
|
|
+ """
|
|
|
+ The FileAttributes tag defines characteristics of the SWF file. This tag
|
|
|
+ is required for SWF 8 and later and must be the first tag in the SWF file.
|
|
|
+ Additionally, the FileAttributes tag can optionally be included in all SWF
|
|
|
+ file versions.
|
|
|
+ The HasMetadata flag identifies whether the SWF file contains the Metadata
|
|
|
+ tag. Flash Player does not care about this bit field or the related tag but
|
|
|
+ it is useful for search engines.
|
|
|
+ The UseNetwork flag signifies whether Flash Player should grant the SWF file
|
|
|
+ local or network file access if the SWF file is loaded locally. The default
|
|
|
+ behavior is to allow local SWF files to interact with local files only, and
|
|
|
+ not with the network. However, by setting the UseNetwork flag, the local SWF
|
|
|
+ can forfeit its local file system access in exchange for access to the
|
|
|
+ network. Any version of SWF can use the UseNetwork flag to set the file
|
|
|
+ access for locally loaded SWF files that are running in Flash Player 8 or later.
|
|
|
+ """
|
|
|
+ TYPE = 69
|
|
|
+ def __init__(self):
|
|
|
+ super(TagFileAttributes, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "FileAttributes"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagFileAttributes.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 8
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ flags = data.readUI8()
|
|
|
+ self.useDirectBlit = ((flags & 0x40) != 0)
|
|
|
+ self.useGPU = ((flags & 0x20) != 0)
|
|
|
+ self.hasMetadata = ((flags & 0x10) != 0)
|
|
|
+ self.actionscript3 = ((flags & 0x08) != 0)
|
|
|
+ self.useNetwork = ((flags & 0x01) != 0)
|
|
|
+ data.skip_bytes(3)
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagFileAttributes, self).__str__() + \
|
|
|
+ " useDirectBlit: %d, " % self.useDirectBlit + \
|
|
|
+ "useGPU: %d, " % self.useGPU + \
|
|
|
+ "hasMetadata: %d, " % self.hasMetadata + \
|
|
|
+ "actionscript3: %d, " % self.actionscript3 + \
|
|
|
+ "useNetwork: %d" % self.useNetwork
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagPlaceObject3(TagPlaceObject2):
|
|
|
+ TYPE = 70
|
|
|
+ def __init__(self):
|
|
|
+ super(TagPlaceObject3, self).__init__()
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ flags = data.readUI8()
|
|
|
+ self.hasClipActions = ((flags & 0x80) != 0)
|
|
|
+ self.hasClipDepth = ((flags & 0x40) != 0)
|
|
|
+ self.hasName = ((flags & 0x20) != 0)
|
|
|
+ self.hasRatio = ((flags & 0x10) != 0)
|
|
|
+ self.hasColorTransform = ((flags & 0x08) != 0)
|
|
|
+ self.hasMatrix = ((flags & 0x04) != 0)
|
|
|
+ self.hasCharacter = ((flags & 0x02) != 0)
|
|
|
+ self.hasMove = ((flags & 0x01) != 0)
|
|
|
+ flags2 = data.readUI8();
|
|
|
+ self.hasImage = ((flags2 & 0x10) != 0)
|
|
|
+ self.hasClassName = ((flags2 & 0x08) != 0)
|
|
|
+ self.hasCacheAsBitmap = ((flags2 & 0x04) != 0)
|
|
|
+ self.hasBlendMode = ((flags2 & 0x2) != 0)
|
|
|
+ self.hasFilterList = ((flags2 & 0x1) != 0)
|
|
|
+ self.depth = data.readUI16()
|
|
|
+
|
|
|
+ if self.hasClassName:
|
|
|
+ self.className = data.readString()
|
|
|
+ if self.hasCharacter:
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ if self.hasMatrix:
|
|
|
+ self.matrix = data.readMATRIX()
|
|
|
+ if self.hasColorTransform:
|
|
|
+ self.colorTransform = data.readCXFORMWITHALPHA()
|
|
|
+ if self.hasRatio:
|
|
|
+ self.ratio = data.readUI16()
|
|
|
+ if self.hasName:
|
|
|
+ self.instanceName = data.readString()
|
|
|
+ if self.hasClipDepth:
|
|
|
+ self.clipDepth = data.readUI16();
|
|
|
+ if self.hasFilterList:
|
|
|
+ numberOfFilters = data.readUI8()
|
|
|
+ for i in range(0, numberOfFilters):
|
|
|
+ self._surfaceFilterList.append(data.readFILTER())
|
|
|
+ if self.hasBlendMode:
|
|
|
+ self.blendMode = data.readUI8()
|
|
|
+ if self.hasCacheAsBitmap:
|
|
|
+ self.bitmapCache = data.readUI8()
|
|
|
+ if self.hasClipActions:
|
|
|
+ self.clipActions = data.readCLIPACTIONS(version)
|
|
|
+ #raise Exception("PlaceObject3: ClipActions not yet implemented!")
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "PlaceObject3"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagPlaceObject3.TYPE
|
|
|
+
|
|
|
+class TagDefineFontAlignZones(Tag):
|
|
|
+ TYPE = 73
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineFontAlignZones, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineFontAlignZones"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineFontAlignZones.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 8
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.zoneTable = []
|
|
|
+
|
|
|
+ self.fontId = data.readUI16()
|
|
|
+ self.csmTableHint = (data.readUI8() >> 6)
|
|
|
+
|
|
|
+ recordsEndPos = data.tell() + length - 3;
|
|
|
+ while data.tell() < recordsEndPos:
|
|
|
+ self.zoneTable.append(data.readZONERECORD())
|
|
|
+
|
|
|
+class TagCSMTextSettings(Tag):
|
|
|
+ TYPE = 74
|
|
|
+ def __init__(self):
|
|
|
+ super(TagCSMTextSettings, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "CSMTextSettings"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagCSMTextSettings.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 8
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.textId = data.readUI16()
|
|
|
+ self.useFlashType = data.readUB(2)
|
|
|
+ self.gridFit = data.readUB(3);
|
|
|
+ data.readUB(3) # reserved, always 0
|
|
|
+ self.thickness = data.readFIXED()
|
|
|
+ self.sharpness = data.readFIXED()
|
|
|
+ data.readUI8() # reserved, always 0
|
|
|
+
|
|
|
+class TagDefineFont3(TagDefineFont2):
|
|
|
+ TYPE = 75
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineFont3, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineFont3"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineFont3.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 2
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 8
|
|
|
+
|
|
|
+class TagSymbolClass(Tag):
|
|
|
+ TYPE = 76
|
|
|
+ def __init__(self):
|
|
|
+ self.symbols = []
|
|
|
+ super(TagSymbolClass, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "SymbolClass"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagSymbolClass.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 9 # educated guess (not specified in SWF10 spec)
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.symbols = []
|
|
|
+ numSymbols = data.readUI16()
|
|
|
+ for i in range(0, numSymbols):
|
|
|
+ self.symbols.append(data.readSYMBOL())
|
|
|
+
|
|
|
+class TagMetadata(Tag):
|
|
|
+ TYPE = 77
|
|
|
+ def __init__(self):
|
|
|
+ super(TagMetadata, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "Metadata"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagMetadata.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.xmlString = data.readString()
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagMetadata, self).__str__()
|
|
|
+ s += " xml: %r" % self.xmlString
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagDoABC(Tag):
|
|
|
+ TYPE = 82
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDoABC, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DoABC"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDoABC.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 9
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ pos = data.tell()
|
|
|
+ flags = data.readUI32()
|
|
|
+ self.lazyInitializeFlag = ((flags & 0x01) != 0)
|
|
|
+ self.abcName = data.readString()
|
|
|
+ self.bytes = data.f.read(length - (data.tell() - pos))
|
|
|
+
|
|
|
+class TagDefineShape4(TagDefineShape3):
|
|
|
+ TYPE = 83
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineShape4, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineShape4"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineShape4.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 4
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 8
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self._shape_bounds = data.readRECT()
|
|
|
+ self.edge_bounds = data.readRECT()
|
|
|
+ flags = data.readUI8()
|
|
|
+ self.uses_fillwinding_rule = ((flags & 0x04) != 0)
|
|
|
+ self.uses_non_scaling_strokes = ((flags & 0x02) != 0)
|
|
|
+ self.uses_scaling_strokes = ((flags & 0x01) != 0)
|
|
|
+ self._shapes = data.readSHAPEWITHSTYLE(self.level)
|
|
|
+
|
|
|
+class TagDefineSceneAndFrameLabelData(Tag):
|
|
|
+ TYPE = 86
|
|
|
+ def __init__(self):
|
|
|
+ self.scenes = []
|
|
|
+ self.frameLabels = []
|
|
|
+ super(TagDefineSceneAndFrameLabelData, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineSceneAndFrameLabelData"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineSceneAndFrameLabelData.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 9
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.sceneCount = data.readEncodedU32()
|
|
|
+
|
|
|
+ if self.sceneCount >= 0x80000000:
|
|
|
+ #print "WARNING: Negative sceneCount value: %x found!. SWF file exploiting CVE-2007-0071?" % self.sceneCount
|
|
|
+ return
|
|
|
+
|
|
|
+ self.scenes = []
|
|
|
+ self.frameLabels = []
|
|
|
+ for i in range(0, self.sceneCount):
|
|
|
+ sceneOffset = data.readEncodedU32()
|
|
|
+ sceneName = data.readString()
|
|
|
+ self.scenes.append(SWFScene(sceneOffset, sceneName))
|
|
|
+
|
|
|
+ frameLabelCount = data.readEncodedU32()
|
|
|
+ for i in range(0, frameLabelCount):
|
|
|
+ frameNumber = data.readEncodedU32();
|
|
|
+ frameLabel = data.readString();
|
|
|
+ self.frameLabels.append(SWFFrameLabel(frameNumber, frameLabel))
|
|
|
+
|
|
|
+class TagDefineBinaryData(DefinitionTag):
|
|
|
+ """
|
|
|
+ The DefineBinaryData tag permits arbitrary binary data to be embedded in a SWF file. DefineBinaryData is a definition tag, like DefineShape and DefineSprite. It associates a blob of binary data with a standard SWF 16-bit character ID. The character ID is entered into the SWF file's character dictionary. DefineBinaryData is intended to be used in conjunction with the SymbolClass tag. The SymbolClass tag can be used to associate a DefineBinaryData tag with an AS3 class definition. The AS3 class must be a subclass of ByteArray. When the class is instantiated, it will be populated automatically with the contents of the binary data resource.
|
|
|
+ """
|
|
|
+ TYPE = 87
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineBinaryData, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineBinaryData"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineBinaryData.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.reserved = data.readUI32()
|
|
|
+ self.data = data.read(length - 6)
|
|
|
+
|
|
|
+class TagDefineFontName(Tag):
|
|
|
+ TYPE = 88
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineFontName, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "DefineFontName"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineFontName.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 9
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagDefineFontName, self).get_dependencies()
|
|
|
+ s.add(self.fontId)
|
|
|
+ return s
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.fontId = data.readUI16()
|
|
|
+ self.fontName = data.readString()
|
|
|
+ self.fontCopyright = data.readString()
|
|
|
+
|
|
|
+class TagDefineSound(Tag):
|
|
|
+ TYPE = 14
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineSound, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDefineSound"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineSound.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ assert length > 7
|
|
|
+ self.soundId = data.readUI16()
|
|
|
+ self.soundFormat = data.readUB(4)
|
|
|
+ self.soundRate = data.readUB(2)
|
|
|
+ self.soundSampleSize = data.readUB(1)
|
|
|
+ self.soundChannels = data.readUB(1)
|
|
|
+ self.soundSamples = data.readUI32()
|
|
|
+ # used 2 + 1 + 4 bytes here
|
|
|
+ self.soundData = BytesIO(data.read(length - 7))
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagDefineSound, self).__str__()
|
|
|
+ s += " soundFormat: %s" % AudioCodec.tostring(self.soundFormat)
|
|
|
+ s += " soundRate: %s" % AudioSampleRate.tostring(self.soundRate)
|
|
|
+ s += " soundSampleSize: %s" % AudioSampleSize.tostring(self.soundSampleSize)
|
|
|
+ s += " soundChannels: %s" % AudioChannels.tostring(self.soundChannels)
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagStartSound(Tag):
|
|
|
+ TYPE = 15
|
|
|
+ def __init__(self):
|
|
|
+ super(TagStartSound, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagStartSound"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagStartSound.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.soundId = data.readUI16()
|
|
|
+ self.soundInfo = data.readSOUNDINFO()
|
|
|
+
|
|
|
+class TagStartSound2(Tag):
|
|
|
+ TYPE = 89
|
|
|
+ def __init__(self):
|
|
|
+ super(TagStartSound2, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagStartSound2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagStartSound2.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 9
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.soundClassName = data.readString()
|
|
|
+ self.soundInfo = data.readSOUNDINFO()
|
|
|
+
|
|
|
+class TagSoundStreamHead(Tag):
|
|
|
+ TYPE = 18
|
|
|
+ def __init__(self):
|
|
|
+ super(TagSoundStreamHead, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagSoundStreamHead"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagSoundStreamHead.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def level(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 1
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ # byte 1
|
|
|
+ self.reserved0 = data.readUB(4)
|
|
|
+ self.playbackRate = data.readUB(2)
|
|
|
+ self.playbackSampleSize = data.readUB(1)
|
|
|
+ self.playbackChannels = data.readUB(1)
|
|
|
+
|
|
|
+ # byte 2
|
|
|
+ self.soundFormat = data.readUB(4)
|
|
|
+ self.soundRate = data.readUB(2)
|
|
|
+ self.soundSampleSize = data.readUB(1)
|
|
|
+ self.soundChannels = data.readUB(1)
|
|
|
+
|
|
|
+ self.samples = data.readUI16()
|
|
|
+ self.latencySeek = data.readSI16() if self.soundFormat == AudioCodec.MP3 else None
|
|
|
+ hdr = 6 if self.soundFormat == AudioCodec.MP3 else 4
|
|
|
+ assert hdr == length
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagSoundStreamHead, self).__str__()
|
|
|
+ s += " playbackRate: %s" % AudioSampleRate.tostring(self.playbackRate)
|
|
|
+ s += " playbackSampleSize: %s" % AudioSampleSize.tostring(self.playbackSampleSize)
|
|
|
+ s += " playbackChannels: %s" % AudioChannels.tostring(self.playbackChannels)
|
|
|
+ s += " soundFormat: %s" % AudioCodec.tostring(self.soundFormat)
|
|
|
+ s += " soundRate: %s" % AudioSampleRate.tostring(self.soundRate)
|
|
|
+ s += " soundSampleSize: %s" % AudioSampleSize.tostring(self.soundSampleSize)
|
|
|
+ s += " soundChannels: %s" % AudioChannels.tostring(self.soundChannels)
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagSoundStreamHead2(TagSoundStreamHead):
|
|
|
+ """
|
|
|
+ The SoundStreamHead2 tag is identical to the SoundStreamHead tag, except it allows
|
|
|
+ different values for StreamSoundCompression and StreamSoundSize (SWF 3 file format).
|
|
|
+ """
|
|
|
+ TYPE = 45
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagSoundStreamHead2, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagSoundStreamHead2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagSoundStreamHead2.TYPE
|
|
|
+
|
|
|
+class TagSoundStreamBlock(Tag):
|
|
|
+ """
|
|
|
+ The SoundStreamHead2 tag is identical to the SoundStreamHead tag, except it allows
|
|
|
+ different values for StreamSoundCompression and StreamSoundSize (SWF 3 file format).
|
|
|
+ """
|
|
|
+ TYPE = 19
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagSoundStreamBlock, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagSoundStreamBlock"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagSoundStreamBlock.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ # unfortunately we can't see our associated SoundStreamHead from here,
|
|
|
+ # so just stash the data
|
|
|
+ self.data = BytesIO(data.read(length))
|
|
|
+
|
|
|
+ def complete_parse_with_header(self, head):
|
|
|
+ stream = SWFStream(self.data)
|
|
|
+ if head.soundFormat in (AudioCodec.UncompressedNativeEndian,
|
|
|
+ AudioCodec.UncompressedLittleEndian):
|
|
|
+ pass # data is enough
|
|
|
+ elif head.soundFormat == AudioCodec.MP3:
|
|
|
+ self.sampleCount = stream.readUI16()
|
|
|
+ self.seekSize = stream.readSI16()
|
|
|
+ self.mpegFrames = stream.read()
|
|
|
+
|
|
|
+class TagDefineBinaryData(DefinitionTag):
|
|
|
+ """
|
|
|
+ The DefineBinaryData tag permits arbitrary binary data to be embedded in a SWF file.
|
|
|
+ DefineBinaryData is a definition tag, like DefineShape and DefineSprite. It associates a blob
|
|
|
+ of binary data with a standard SWF 16-bit character ID. The character ID is entered into the
|
|
|
+ SWF file's character dictionary.
|
|
|
+ """
|
|
|
+ TYPE = 87
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineBinaryData, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDefineBinaryData"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineBinaryData.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ assert length >= 6
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.reserved = data.readUI32()
|
|
|
+ self.data = data.read(length - 4 - 2)
|
|
|
+
|
|
|
+class TagProductInfo(Tag):
|
|
|
+ """
|
|
|
+ Undocumented in SWF10.
|
|
|
+ """
|
|
|
+ TYPE = 41
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagProductInfo, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagProductInfo"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagProductInfo.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.product = data.readUI32()
|
|
|
+ self.edition = data.readUI32()
|
|
|
+ self.majorVersion, self.minorVersion = data.readUI8(), data.readUI8()
|
|
|
+ self.build = data.readUI64()
|
|
|
+ self.compileTime = data.readUI64()
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagProductInfo, self).__str__()
|
|
|
+ s += " product: %s" % ProductKind.tostring(self.product)
|
|
|
+ s += " edition: %s" % ProductEdition.tostring(self.edition)
|
|
|
+ s += " major.minor.build: %d.%d.%d" % (self.majorVersion, self.minorVersion, self.build)
|
|
|
+ s += " compileTime: %d" % (self.compileTime)
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagScriptLimits(Tag):
|
|
|
+ """
|
|
|
+ The ScriptLimits tag includes two fields that can be used to override the default settings for
|
|
|
+ maximum recursion depth and ActionScript time-out: MaxRecursionDepth and
|
|
|
+ ScriptTimeoutSeconds.
|
|
|
+ """
|
|
|
+ TYPE = 65
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagScriptLimits, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagScriptLimits"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagScriptLimits.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.maxRecursionDepth = data.readUI16()
|
|
|
+ self.scriptTimeoutSeconds = data.readUI16()
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagScriptLimits, self).__str__()
|
|
|
+ s += " maxRecursionDepth: %s" % self.maxRecursionDepth
|
|
|
+ s += " scriptTimeoutSeconds: %s" % self.scriptTimeoutSeconds
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagDebugID(Tag):
|
|
|
+ """
|
|
|
+ Undocumented in SWF10. Some kind of GUID.
|
|
|
+ """
|
|
|
+ TYPE = 63
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDebugID, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDebugID"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDebugID.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.guid = data.read(16)
|
|
|
+
|
|
|
+class TagExportAssets(Tag):
|
|
|
+ """
|
|
|
+ The ExportAssets tag makes portions of a SWF file available for import by other SWF files
|
|
|
+ """
|
|
|
+ TYPE = 56
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagExportAssets, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagExportAssets"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 5
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagExportAssets.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.count = data.readUI16()
|
|
|
+ self.exports = [data.readEXPORT() for i in range(self.count)]
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagExportAssets, self).__str__()
|
|
|
+ s += " exports: %s" % self.exports
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagProtect(Tag):
|
|
|
+ """
|
|
|
+ The Protect tag marks a file as not importable for editing in an authoring environment. If the
|
|
|
+ Protect tag contains no data (tag length = 0), the SWF file cannot be imported. If this tag is
|
|
|
+ present in the file, any authoring tool should prevent the file from loading for editing.
|
|
|
+ """
|
|
|
+ TYPE = 24
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagProtect, self).__init__()
|
|
|
+ self.password = None
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagProtect"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 2 if self.password is None else 5
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagProtect.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ if length:
|
|
|
+ self.password = data.readString()
|
|
|
+ else:
|
|
|
+ self.password = None
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagProtect, self).__str__()
|
|
|
+ s += " password: %r" % self.password
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagEnableDebugger(Tag):
|
|
|
+ """
|
|
|
+ The EnableDebugger tag enables debugging. The password in the EnableDebugger tag is
|
|
|
+ encrypted by using the MD5 algorithm, in the same way as the Protect tag.
|
|
|
+ """
|
|
|
+ TYPE = 58
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagEnableDebugger, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagEnableDebugger"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 5
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagEnableDebugger.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.password = data.readString()
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagEnableDebugger, self).__str__()
|
|
|
+ s += " password: %r" % self.password
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagEnableDebugger2(Tag):
|
|
|
+ """
|
|
|
+ The EnableDebugger2 tag enables debugging. The Password field is encrypted by using the
|
|
|
+ MD5 algorithm, in the same way as the Protect tag.
|
|
|
+ """
|
|
|
+ TYPE = 64
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagEnableDebugger2, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagEnableDebugger2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 6
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagEnableDebugger2.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.reserved0 = data.readUI16()
|
|
|
+ self.password = data.readString()
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ s = super(TagEnableDebugger2, self).__str__()
|
|
|
+ s += " password: %r" % self.password
|
|
|
+ return s
|
|
|
+
|
|
|
+class TagDoInitAction(Tag):
|
|
|
+ """
|
|
|
+ The DoInitAction tag is similar to the DoAction tag: it defines a series of bytecodes to be
|
|
|
+ executed. However, the actions defined with DoInitAction are executed earlier than the usual
|
|
|
+ DoAction actions, and are executed only once.
|
|
|
+ """
|
|
|
+ TYPE = 59
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDoInitAction, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDoInitAction"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 6
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDoInitAction.TYPE
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagDoInitAction, self).get_dependencies()
|
|
|
+ s.add(self.spriteId)
|
|
|
+ return s
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.spriteId = data.readUI16()
|
|
|
+ self.actions = data.readACTIONRECORDs()
|
|
|
+
|
|
|
+class TagDefineEditText(DefinitionTag):
|
|
|
+ """
|
|
|
+ The DefineEditText tag defines a dynamic text object, or text field.
|
|
|
+
|
|
|
+ A text field is associated with an ActionScript variable name where the contents of the text
|
|
|
+ field are stored. The SWF file can read and write the contents of the variable, which is always
|
|
|
+ kept in sync with the text being displayed. If the ReadOnly flag is not set, users may change
|
|
|
+ the value of a text field interactively
|
|
|
+ """
|
|
|
+ TYPE = 37
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineEditText, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDefineEditText"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineEditText.TYPE
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagDefineEditText, self).get_dependencies()
|
|
|
+ s.add(self.fontId) if self.hasFont else None
|
|
|
+ return s
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.bounds = data.readRECT()
|
|
|
+
|
|
|
+ # flags
|
|
|
+ self.hasText = data.readUB(1) == 1
|
|
|
+ self.wordWrap = data.readUB(1) == 1
|
|
|
+ self.multiline = data.readUB(1) == 1
|
|
|
+ self.password = data.readUB(1) == 1
|
|
|
+
|
|
|
+ self.readOnly = data.readUB(1) == 1
|
|
|
+ self.hasTextColor = data.readUB(1) == 1
|
|
|
+ self.hasMaxLength = data.readUB(1) == 1
|
|
|
+ self.hasFont = data.readUB(1) == 1
|
|
|
+
|
|
|
+ self.hasFontClass = data.readUB(1) == 1
|
|
|
+ self.autoSize = data.readUB(1) == 1
|
|
|
+ self.hasLayout = data.readUB(1) == 1
|
|
|
+ self.noSelect = data.readUB(1) == 1
|
|
|
+
|
|
|
+ self.border = data.readUB(1) == 1
|
|
|
+ self.wasStatic = data.readUB(1) == 1
|
|
|
+ self.html = data.readUB(1) == 1
|
|
|
+ self.useOutlines = data.readUB(1) == 1
|
|
|
+
|
|
|
+ # values
|
|
|
+ self.fontId = data.readUI16() if self.hasFont else None
|
|
|
+ self.fontClass = data.readString() if self.hasFontClass else None
|
|
|
+ self.fontHeight = data.readUI16() if self.hasFont else None
|
|
|
+ self.textColor = data.readRGBA() if self.hasTextColor else None
|
|
|
+ self.maxLength = data.readUI16() if self.hasMaxLength else None
|
|
|
+
|
|
|
+ self.align = data.readUI8() if self.hasLayout else None
|
|
|
+ self.leftMargin = data.readUI16() if self.hasLayout else None
|
|
|
+ self.rightMargin = data.readUI16() if self.hasLayout else None
|
|
|
+ self.indent = data.readUI16() if self.hasLayout else None
|
|
|
+ self.leading = data.readUI16() if self.hasLayout else None
|
|
|
+
|
|
|
+ # backend info
|
|
|
+ self.variableName = data.readString()
|
|
|
+ self.initialText = data.readString() if self.hasText else None
|
|
|
+
|
|
|
+class TagDefineButton(DefinitionTag):
|
|
|
+ """
|
|
|
+ The DefineButton tag defines a button character for later use by control tags such as
|
|
|
+ PlaceObject.
|
|
|
+ """
|
|
|
+ TYPE = 7
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineButton, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDefineButton"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineButton.TYPE
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagDefineButton, self).get_dependencies()
|
|
|
+ for b in self.buttonCharacters:
|
|
|
+ s.update(b.get_dependencies())
|
|
|
+ return s
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.buttonCharacters = data.readBUTTONRECORDs(version = 1)
|
|
|
+ self.buttonActions = data.readACTIONRECORDs()
|
|
|
+
|
|
|
+class TagDefineButton2(DefinitionTag):
|
|
|
+ """
|
|
|
+ DefineButton2 extends the capabilities of DefineButton by allowing any state transition to
|
|
|
+ trigger actions.
|
|
|
+ """
|
|
|
+ TYPE = 34
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineButton2, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDefineButton2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineButton2.TYPE
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagDefineButton2, self).get_dependencies()
|
|
|
+ for b in self.buttonCharacters:
|
|
|
+ s.update(b.get_dependencies())
|
|
|
+ return s
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.reservedFlags = data.readUB(7)
|
|
|
+ self.trackAsMenu = data.readUB(1) == 1
|
|
|
+ offs = data.tell()
|
|
|
+ self.actionOffset = data.readUI16()
|
|
|
+ self.buttonCharacters = data.readBUTTONRECORDs(version = 2)
|
|
|
+
|
|
|
+ if self.actionOffset:
|
|
|
+ # if we have actions, seek to the first one
|
|
|
+ data.seek(offs + self.actionOffset)
|
|
|
+ self.buttonActions = data.readBUTTONCONDACTIONSs()
|
|
|
+
|
|
|
+class TagDefineButtonSound(Tag):
|
|
|
+ """
|
|
|
+ The DefineButtonSound tag defines which sounds (if any) are played on state transitions.
|
|
|
+ """
|
|
|
+ TYPE = 17
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineButtonSound, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDefineButtonSound"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineButtonSound.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 2
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.buttonId = data.readUI16()
|
|
|
+
|
|
|
+ for event in 'OverUpToIdle IdleToOverUp OverUpToOverDown OverDownToOverUp'.split():
|
|
|
+ soundId = data.readUI16()
|
|
|
+ setattr(self, 'soundOn' + event, soundId)
|
|
|
+ soundInfo = data.readSOUNDINFO() if soundId else None
|
|
|
+ setattr(self, 'soundInfoOn' + event, soundInfo)
|
|
|
+
|
|
|
+class TagDefineScalingGrid(Tag):
|
|
|
+ """
|
|
|
+ The DefineScalingGrid tag introduces the concept of 9-slice scaling, which allows
|
|
|
+ component-style scaling to be applied to a sprite or button character.
|
|
|
+ """
|
|
|
+ TYPE = 78
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineScalingGrid, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDefineScalingGrid"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineScalingGrid.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.splitter = data.readRECT()
|
|
|
+
|
|
|
+class TagDefineVideoStream(DefinitionTag):
|
|
|
+ """
|
|
|
+ DefineVideoStream defines a video character that can later be placed on the display list.
|
|
|
+ """
|
|
|
+ TYPE = 60
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagDefineVideoStream, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDefineVideoStream"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineVideoStream.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+ self.numFrames = data.readUI16()
|
|
|
+ self.width = data.readUI16()
|
|
|
+ self.height = data.readUI16()
|
|
|
+ reserved0 = data.readUB(4)
|
|
|
+ self.videoDeblocking = data.readUB(3)
|
|
|
+ self.videoSmoothing = data.readUB(1)
|
|
|
+ self.codec = data.readUI8()
|
|
|
+
|
|
|
+class TagVideoFrame(Tag):
|
|
|
+ """
|
|
|
+ VideoFrame provides a single frame of video data for a video character that is already defined
|
|
|
+ with DefineVideoStream.
|
|
|
+ """
|
|
|
+ TYPE = 61
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ super(TagVideoFrame, self).__init__()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagVideoFrame"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagVideoFrame.TYPE
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self.streamId = data.readUI16()
|
|
|
+ self.frameNumber = data.readUI16()
|
|
|
+ self.videoData = data.read(length - 4)
|
|
|
+
|
|
|
+class TagDefineMorphShape2(TagDefineMorphShape):
|
|
|
+ """
|
|
|
+ The DefineMorphShape2 tag extends the capabilities of DefineMorphShape by using a new
|
|
|
+ morph line style record in the morph shape. MORPHLINESTYLE2 allows the use of new
|
|
|
+ types of joins and caps as well as scaling options and the ability to fill the strokes of the morph
|
|
|
+ shape.
|
|
|
+ """
|
|
|
+ TYPE = 84
|
|
|
+
|
|
|
+ @property
|
|
|
+ def name(self):
|
|
|
+ return "TagDefineMorphShape2"
|
|
|
+
|
|
|
+ @property
|
|
|
+ def type(self):
|
|
|
+ return TagDefineMorphShape2.TYPE
|
|
|
+
|
|
|
+ @property
|
|
|
+ def version(self):
|
|
|
+ return 8
|
|
|
+
|
|
|
+ def get_dependencies(self):
|
|
|
+ s = super(TagDefineMorphShape2, self).get_dependencies()
|
|
|
+ s.update(self.startEdges.get_dependencies())
|
|
|
+ s.update(self.endEdges.get_dependencies())
|
|
|
+ return s
|
|
|
+
|
|
|
+ def parse(self, data, length, version=1):
|
|
|
+ self._morphFillStyles = []
|
|
|
+ self._morphLineStyles = []
|
|
|
+ self.characterId = data.readUI16()
|
|
|
+
|
|
|
+ self.startBounds = data.readRECT()
|
|
|
+ self.endBounds = data.readRECT()
|
|
|
+ self.startEdgeBounds = data.readRECT()
|
|
|
+ self.endEdgeBounds = data.readRECT()
|
|
|
+
|
|
|
+ self.reserved0 = data.readUB(6)
|
|
|
+ self.usesNonScalingStrokes = data.readUB(1) == 1
|
|
|
+ self.usesScalingStrokes = data.readUB(1) == 1
|
|
|
+
|
|
|
+ offset = data.readUI32()
|
|
|
+ self._morphFillStyles = data.readMORPHFILLSTYLEARRAY()
|
|
|
+ self._morphLineStyles = data.readMORPHLINESTYLEARRAY(version = 2)
|
|
|
+
|
|
|
+ self.startEdges = data.readSHAPE();
|
|
|
+ self.endEdges = data.readSHAPE();
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ # some table checks
|
|
|
+ for x in range(256):
|
|
|
+ y = TagFactory.create(x)
|
|
|
+ if y:
|
|
|
+ assert y.type == x, y.name + ' is misnamed'
|
|
|
+
|
|
|
+ for k, v in globals().items():
|
|
|
+ if k.startswith('Tag') and hasattr(v, 'TYPE'):
|
|
|
+ y = TagFactory.create(v.TYPE)
|
|
|
+ if y == None:
|
|
|
+ #print v.__name__, 'missing', 'for', v.TYPE
|
|
|
+ pass
|