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