Commits

Alexander Konovalov committed 961868e

First commit

Comments (0)

Files changed (13)

+syntax: glob
+wiki/*
+*.pyc
+#!/usr/bin/python3.2
+
+import sys
+from swf.structure import *
+from swf.reader import SWFReader
+from swf.writer import SWFWriter
+from swf.tags import *   
+
+if __name__ == "__main__":
+    f = open(sys.argv[1], "rb")
+    swf = SWFReader(f).read()
+    f.close()
+    
+    print("Compressed :", swf.header.compressed)
+    print("Version    :", swf.header.version)
+    print("Frame size :", swf.header.frameSize)
+    print("Frame rate :", swf.header.frameRate / 256)
+    print("Frame count:", swf.header.frameCount)
+    
+    for tag in swf.tags:
+        if tag.type == TAG_DOACTION:
+            print("found DOACTION tag")
+        if tag.type == TAG_SHOWFRAME:
+            print("found SHOWFRAME tag")
+    
+    f = open("out.swf", "wb")
+    SWFWriter(f).write(swf)
+    f.close()

bytecode-to-swf.py

+#!/usr/bin/python3.2
+
+import sys
+import base64
+from swf.structure import *
+from swf.reader import SWFReader
+from swf.writer import SWFWriter
+from swf.tags import *   
+
+if __name__ == "__main__":
+    if len(sys.argv) == 3:
+        f = open(sys.argv[2], "r")
+        bytecode = bytes.fromhex(f.read())
+        f.close()
+        
+        f = open(sys.argv[1], "wb")
+        swf = SWF()
+        swf.header.compressed = False
+        swf.header.version = 8
+        swf.header.frameSize.right = 100
+        swf.header.frameSize.top = 100
+        swf.header.frameRate = 20 * 256
+        swf.header.frameCount = 0
+        
+        codeTag = Tag()
+        codeTag.type = TAG_DOACTION
+        codeTag.rawData = bytecode
+        swf.tags.append(codeTag)
+        
+        SWFWriter(f).write(swf)
+        f.close()
+    else:
+        print("Invalid arguments. Please use like this 'bytecode-to-swf.py file bytecode'")
+class MarkingByteReader(ByteReader):
+    def __init__(self, data):
+        super(MarkingByteReader, self).__init__(data)
+        self._marked = [False] * len(data)
+    
+    def marked(self, pos):
+        return self._marked[pos]
+    
+    def take(self, n):
+        for i in range(self.position, self.position + n):
+            self._marked[i] = True
+        return super(MarkingByteReader, self).take(n)
+
+class VirtualMachine:
+    NULL = object()
+    UNDEFINED = object()
+    UNKNOWN = object()
+
+    def __init__(self):
+        self.clear()
+    
+    def clear(self):
+        self.stack = []
+    
+    def push(self, val):
+        self.stack.append(val)
+    
+    def pop(self):
+        if len(self.stack) > 0:
+            return self.stack.pop()
+        else:
+            return VirtualMachine.UNKNOWN
+    
+    def _isnumeric(self, val):
+        # XXX
+        if (val == VirtualMachine.NULL or 
+                   val == VirtualMachine.UNDEFINED or
+                   val == VirtualMachine.UNKNOWN):
+            return False
+        
+        return True
+    
+    def subtract(self):
+        a = self.pop()
+        b = self.pop()
+        
+        if self._isnumeric(a) and self._isnumeric(b):
+            print "b - a = " + str(b) + " - " + str(a) + " = " + str(b - a)
+            self.stack.append(b - a)
+        else:
+            self.stack.append(VirtualMachine.UNKNOWN)
+     
+    def add2(self):
+        a = self.pop()
+        b = self.pop()
+        
+        if self._isnumeric(a) and self._isnumeric(b):
+            print "a + b = " + str(a) + " + " + str(b) + " = " + str(b + a)
+            self.stack.append(a + b)
+        else:
+            self.stack.append(VirtualMachine.UNKNOWN)
+     
+    def toBool(self):
+        c = self.pop()
+        if self._isnumeric(c):
+            print "isTrue on known const jump : " + str(c) 
+            return 1 if c != 0 else -1
+        else: return 0
+
+class Deobfuscator:
+    def __init__(self, data):
+        self.reader = MarkingByteReader(data)
+        self.vm = VirtualMachine()
+        self.queue = [0]
+    
+    def readAction(self):
+        code = self.reader.ui8()
+        data = None
+        
+        if code > 0x80:
+            length = self.reader.uli16()
+            data = self.reader.take(length)
+        else:
+            data = []
+        
+        return (code, data)
+    
+    def run(self):
+        while len(self.queue) > 0:
+            self.reader.position = self.queue.pop(0)
+            if self.reader.marked(self.reader.position):
+                continue
+            self.vm.clear()
+            
+            stop = False
+            
+            while not stop:
+                code, data = self.readAction()
+                
+                if code == SWFACTION_PUSH:
+                    r = ByteReader(data)
+                    t = r.ui8()
+                    
+                    if t == 0: # string
+                        #self.vm.push(r.cs())
+                        self.vm.push(VirtualMachine.UNKNOWN)
+                    elif t == 1: # float
+                        self.vm.push(r.lf32())
+                    elif t == 2: # null
+                        #self.vm.push(VirtualMachine.NULL)
+                        self.vm.push(VirtualMachine.UNKNOWN)
+                    elif t == 3: # undefined
+                        #self.vm.push(VirtualMachine.UNDEFINED)
+                        self.vm.push(VirtualMachine.UNKNOWN)
+                    elif t == 4: # register
+                        #self.vm.pushReg(r.ul8())
+                        self.vm.push(VirtualMachine.UNKNOWN)
+                    elif t == 5: # bool
+                        #self.vm.push(r.b8())
+                        self.vm.push(VirtualMachine.UNKNOWN)
+                    elif t == 6: # double
+                        self.vm.push(r.lf64())
+                    elif t == 7: # int32
+                        self.vm.push(r.li32())
+                    elif t == 8: # const pool (8-bit)
+                        #self.vm.
+                        self.vm.push(VirtualMachine.UNKNOWN)
+                    elif t == 9: # const pool (16-bit)
+                        #
+                        self.vm.push(VirtualMachine.UNKNOWN)
+                elif code == SWFACTION_NEWADD:
+                    self.vm.add2()
+                elif code == SWFACTION_SUBTRACT:
+                    self.vm.subtract()
+                elif code == SWFACTION_POP:
+                    self.vm.pop()
+                elif code == SWFACTION_BRANCHALWAYS:
+                    r = ByteReader(data)
+                    self.queue.append(self.reader.position + r.li16())
+                    stop = True
+                elif code == SWFACTION_BRANCHIFTRUE:
+                    r = ByteReader(data)
+                    c = self.vm.toBool()
+                    if c == 1:
+                        self.queue.append(self.reader.position + r.li16())
+                        stop = True
+                    elif c == -1:
+                        rdata = self.reader.data
+                        rpos = self.reader.position
+                        self.reader.data = rdata[:rpos-5] + str(bytearray([SWFACTION_POP] + [SWFACTION_PLAY] * 4)) + rdata[rpos:]
+                    elif c == 0:
+                        self.queue.append(self.reader.position + r.li16())
+                elif code == SWFACTION_END:
+                    stop = True
+                else:
+                    # XXX: know idea what's there
+                    self.vm.clear()
+                    
+        result = bytearray(self.reader.data)
+        for i in range(len(self.reader.data)):
+            if not self.reader.marked(i):
+                #print str(i) + " not marked"
+                result[i] = SWFACTION_PLAY
+        return str(result)

swf/__init__.py

Empty file added.
+def _parseSigned(n, bits):
+    """
+    Parses a signed number stored in the usual two-complement form
+    
+    >>> bits._parseSigned(0xFF, 8)
+    -1
+    >>> bits._parseSigned(0xFE, 8)
+    -2
+    >>> bits._parseSigned(0x10, 8)
+    16
+    """
+    if n >= (1 << (bits - 1)):
+        return n - (1 << bits)
+    else:
+        return n
+
+def _getBit(n, bit):
+    return (n >> bit) & 0x01
+
+def _setBit(n, bit, val):
+    return n | (val << bit)
+
+class BitReader(object):
+    def __init__(self, baseIO):
+        self.baseIO = baseIO
+        
+        self.buffer = 0
+        self.position = -1
+    
+    def readb(self):
+        """
+        Reads a single bit.
+        """
+        if self.position < 0:
+            self.buffer = self.baseIO.read(1)[0]
+            self.position = 7
+        
+        result = _getBit(self.buffer, self.position)
+        self.position -= 1
+        return result
+    
+    def read(self, bits):
+        """
+        Reads an unsigned integer stored as bit field of a given length.
+        """
+        result = 0
+        for i in range(bits):
+            result = _setBit(result, (bits - 1) - i, self.readb())
+        return result
+    
+    def reads(self, bits):
+        """
+        Reads a signed integer stored as bit field of a given length.
+        """
+        return _parseSigned(self.read(bits), bits)
+
+class BitWriter(object):
+    def __init__(self, baseIO):
+        self.baseIO = baseIO
+        
+        self.buffer = 0
+        self.position = 7
+    
+    def writeb(self, bit):
+        """
+        Writes a single bit.
+        """
+        self.buffer = _setBit(self.buffer, self.position, bit)
+        self.position -= 1
+        
+        if self.position < 0:
+            self.baseIO.write(bytes((self.buffer,)))
+            self.position = 7
+            self.buffer = 0
+    
+    def write(self, n, bits):
+        for i in range(bits):
+            self.writeb(_getBit(n, (bits - 1) - i))
+    
+    def writes(self, n, bits):
+        self.write(n, bits)
+    
+    def flush(self):
+        self.baseIO.write(bytes((self.buffer,)))
+        self.position = 7
+        self.buffer = 0

swf/compressionio.py

+import zlib
+import io
+
+class DeflatorIO(io.IOBase):
+    def __init__(self, baseIO):
+        self.baseIO = baseIO
+        self.closed = self.baseIO.closed
+        self.buffer = bytearray()
+        self.dobj = zlib.decompressobj()
+    
+    def read(self, n = -1):
+        if n == -1:
+            self.buffer.append(self.dobj.decompress(self.baseIO.read(-1)))
+            return bytes(self.buffer)
+        else:
+            while len(self.buffer) < n:
+                self.buffer.append(self.dobj.decompress(self.baseIO.read(1024)))
+            result, self.buffer = self.buffer[:n], self.buffer[n:]
+            return result
+from struct import *
+
+class DataReader(object):
+    def __init__(self, baseIO):
+        self.baseIO = baseIO
+        
+    def read(self, n):
+        return self.baseIO.read(n)
+    
+    def readf(self, fmt):
+        size = calcsize(fmt)
+        return unpack(fmt, self.read(size))
+    
+    def uli8(self):  return self.readf("<B")[0]
+    def uli16(self): return self.readf("<H")[0]
+    def uli32(self): return self.readf("<I")[0]
+    
+    def li8(self):   return self.readf("<b")[0]
+    def li16(self):  return self.readf("<h")[0]
+    def li32(self):  return self.readf("<i")[0]
+    
+    def lf32(self):  return self.readf("<f")[0]
+    def lf64(self):  return self.readf("<d")[0]
+    
+    def b8(self):    return self.readf("?")[0]
+    
+    def c(self):     return self.read(1)[0]
+    
+    def cs(self):
+        result = b""
+        while True:
+            char = c()
+            if (char == 0):
+                break
+            result += char
+
+class DataWriter(object):
+    def __init__(self, baseIO):
+        self.baseIO = baseIO
+        
+    def write(self, data):
+        return self.baseIO.write(data)
+    
+    def writef(self, fmt, *values):
+        self.write(pack(fmt, *values))
+    
+    def uli8(self, v):  return self.writef("<B", v)
+    def uli16(self, v): return self.writef("<H", v)
+    def uli32(self, v): return self.writef("<I", v)
+    
+    def li8(self, v):   return self.writef("<b", v)
+    def li16(self, v):  return self.writef("<h", v)
+    def li32(self, v):  return self.writef("<i", v)
+    
+    def lf32(self, v):  return self.writef("<f", v)
+    def lf64(self, v):  return self.writef("<d", v)
+    
+    def b8(self, v):    return self.writef("?", v)
+    
+    def c(self, v):     return self.write(bytes((v,)))
+    
+    def cs(self, v):
+        self.write(v)
+        self.write(bytes((0,)))
+SWFACTION_END						= 0x00
+
+#/* v3 actions */
+SWFACTION_NEXTFRAME					= 0x04
+SWFACTION_PREVFRAME					= 0x05
+SWFACTION_PLAY						= 0x06
+SWFACTION_STOP						= 0x07
+SWFACTION_TOGGLEQUALITY				= 0x08
+SWFACTION_STOPSOUNDS				= 0x09
+SWFACTION_GOTOFRAME					= 0x81	#/* >= 0x80 means record	has	args */
+SWFACTION_GETURL					= 0x83
+SWFACTION_IFFRAMELOADED				= 0x8A
+SWFACTION_SETTARGET					= 0x8B
+SWFACTION_GOTOLABEL					= 0x8C
+
+#/* v4 actions */
+SWFACTION_ADD						= 0x0A
+SWFACTION_SUBTRACT					= 0x0B
+SWFACTION_MULTIPLY					= 0x0C
+SWFACTION_DIVIDE					= 0x0D
+SWFACTION_EQUALS					= 0x0E
+SWFACTION_LESSTHAN					= 0x0F
+SWFACTION_LOGICALAND				= 0x10
+SWFACTION_LOGICALOR					= 0x11
+SWFACTION_LOGICALNOT				= 0x12
+SWFACTION_STRINGEQ					= 0x13
+SWFACTION_STRINGLENGTH				= 0x14
+SWFACTION_SUBSTRING					= 0x15
+SWFACTION_POP						= 0x17
+SWFACTION_INT						= 0x18
+SWFACTION_GETVARIABLE				= 0x1C
+SWFACTION_SETVARIABLE				= 0x1D
+SWFACTION_SETTARGETEXPRESSION		= 0x20
+SWFACTION_STRINGCONCAT				= 0x21
+SWFACTION_GETPROPERTY				= 0x22
+SWFACTION_SETPROPERTY				= 0x23
+SWFACTION_DUPLICATECLIP				= 0x24
+SWFACTION_REMOVECLIP				= 0x25
+SWFACTION_TRACE						= 0x26
+SWFACTION_STARTDRAGMOVIE			= 0x27
+SWFACTION_STOPDRAGMOVIE				= 0x28
+SWFACTION_STRINGLESSTHAN			= 0x29
+SWFACTION_RANDOM					= 0x30
+SWFACTION_MBLENGTH					= 0x31
+SWFACTION_ORD						= 0x32
+SWFACTION_CHR						= 0x33
+SWFACTION_GETTIMER					= 0x34
+SWFACTION_MBSUBSTRING				= 0x35
+SWFACTION_MBORD						= 0x36
+SWFACTION_MBCHR						= 0x37
+SWFACTION_IFFRAMELOADEDEXPRESSION	= 0x8D
+SWFACTION_PUSH  					= 0x96
+SWFACTION_BRANCHALWAYS				= 0x99
+SWFACTION_GETURL2					= 0x9A
+SWFACTION_BRANCHIFTRUE				= 0x9D
+SWFACTION_CALLFRAME					= 0x9E
+SWFACTION_GOTOEXPRESSION			= 0x9F
+
+#/* v5 actions */
+SWFACTION_DELETE					= 0x3A
+SWFACTION_DELETE2					= 0x3B
+SWFACTION_VAREQUALS					= 0x3C
+SWFACTION_CALLFUNCTION				= 0x3D
+SWFACTION_RETURN					= 0x3E
+SWFACTION_MODULO					= 0x3F
+SWFACTION_NEW						= 0x40
+SWFACTION_VAR						= 0x41
+SWFACTION_INITARRAY					= 0x42
+SWFACTION_INITOBJECT				= 0x43
+SWFACTION_TYPEOF					= 0x44
+SWFACTION_TARGETPATH				= 0x45
+SWFACTION_ENUMERATE					= 0x46
+SWFACTION_NEWADD					= 0x47
+SWFACTION_NEWLESSTHAN				= 0x48
+SWFACTION_NEWEQUALS					= 0x49
+SWFACTION_TONUMBER					= 0x4A
+SWFACTION_TOSTRING					= 0x4B
+SWFACTION_DUP						= 0x4C
+SWFACTION_SWAP						= 0x4D
+SWFACTION_GETMEMBER					= 0x4E
+SWFACTION_SETMEMBER					= 0x4F
+SWFACTION_INCREMENT					= 0x50
+SWFACTION_DECREMENT					= 0x51
+SWFACTION_CALLMETHOD				= 0x52
+SWFACTION_NEWMETHOD					= 0x53
+SWFACTION_BITWISEAND				= 0x60
+SWFACTION_BITWISEOR					= 0x61
+SWFACTION_BITWISEXOR				= 0x62
+SWFACTION_SHIFTLEFT					= 0x63
+SWFACTION_SHIFTRIGHT				= 0x64
+SWFACTION_SHIFTRIGHT2				= 0x65
+SWFACTION_SETREGISTER				= 0x87
+SWFACTION_CONSTANTPOOL				= 0x88
+SWFACTION_WITH						= 0x94
+SWFACTION_DEFINEFUNCTION			= 0x9B
+
+#/* v6 actions */
+SWFACTION_INSTANCEOF				= 0x54
+SWFACTION_ENUMERATEVALUE			= 0x55
+SWFACTION_STRICTEQUALS				= 0x66
+SWFACTION_GREATERTHAN				= 0x67
+SWFACTION_STRINGGREATERTHAN			= 0x68
+SWFACTION_STRICTMODE				= 0x89
+
+#/* v7 actions */
+SWFACTION_CAST						= 0x2B
+SWFACTION_IMPLEMENTS				= 0x2C
+SWFACTION_EXTENDS					= 0x69
+SWFACTION_DEFINEFUNCTION2			= 0x8E
+SWFACTION_TRY						= 0x8F
+SWFACTION_THROW						= 0x2A
+
+#/* FlashLite */
+SWFACTION_FSCOMMAND2				= 0x2D
+from .bitsio import *
+from .dataio import *
+from .structure import *
+
+class SWFReader(object):
+    def __init__(self, baseIO):
+        self.baseIO = baseIO
+        self.dataIO = DataReader(self.baseIO)
+    
+    def readRect(self):
+        rect = Rect()
+        bits = BitReader(self.baseIO)
+        
+        count = bits.read(5)
+        rect.left = bits.read(count)
+        rect.right = bits.read(count)
+        rect.bottom = bits.read(count)
+        rect.top = bits.read(count)
+        
+        return rect
+    
+    def readHeader(self):
+        header = Header()
+        
+        signature = self.dataIO.read(3)
+        if signature == b"FWS":
+            header.compressed = False
+        elif signature == b"CWS":
+            header.compressed = True
+            
+        header.version = self.dataIO.uli8()
+        length = self.dataIO.uli32() # note that we don't store it - it's not exactly usefull
+        header.frameSize = self.readRect()
+        header.frameRate = self.dataIO.uli16()
+        header.frameCount = self.dataIO.uli16()
+        
+        return header
+    
+    def readTag(self):
+        tag = Tag()
+        
+        tagCodeAndLength = self.dataIO.uli16()
+        tag.type = tagCodeAndLength >> 6
+        length = tagCodeAndLength & 63
+        
+        if length == 63:
+            length = self.dataIO.uli32()
+        
+        tag.rawData = self.dataIO.read(length)
+        return tag
+    
+    def read(self):
+        swf = SWF()
+        
+        swf.header = self.readHeader()
+        tag = self.readTag()
+        while tag.type != 0x00:
+            swf.tags.append(tag)
+            tag = self.readTag()
+        
+        return swf
+class Rect(object):
+    def __init__(self):
+        self.left = 0
+        self.right = 0
+        self.bottom = 0
+        self.top = 0
+    
+    def __str__(self):
+        return "Rect({0},{1},{2},{3})".format(self.left, self.right, self.bottom, self.top)
+
+class Header(object):
+    def __init__(self):
+        self.compressed = False
+        self.version = 0
+        self.frameSize = Rect()
+        self.frameRate = 0
+        self.frameCount = 0
+
+class Tag(object):
+    def __init__(self, ):
+        self.type = 0x00
+        self.rawData = b""
+
+class SWF(object):
+    def __init__(self):
+        self.header = Header()
+        self.tags = []
+TAG_END								= 0  #/* end tag for movie clip or swf */
+TAG_SHOWFRAME						= 1  #/* frame is completely described now please show */
+TAG_DEFINESHAPE						= 2
+TAG_FREECHARACTER					= 3
+TAG_PLACEOBJECT						= 4
+TAG_REMOVEOBJECT					= 5
+TAG_DEFINEBITS						= 6
+TAG_DEFINEBUTTON					= 7
+TAG_JPEGTABLES						= 8
+TAG_SETBACKGROUNDCOLOR				= 9
+TAG_DEFINEFONT						= 10
+TAG_DEFINETEXT						= 11
+TAG_DOACTION						= 12 #/* normal action block */
+TAG_DEFINEFONTINFO					= 13
+TAG_DEFINESOUND						= 14
+TAG_STARTSOUND						= 15
+TAG_STOPSOUND						= 16
+TAG_DEFINEBUTTONSOUND				= 17
+TAG_SOUNDSTREAMHEAD					= 18
+TAG_SOUNDSTREAMBLOCK				= 19
+TAG_DEFINEBITSLOSSLESS				= 20
+TAG_DEFINEBITSJPEG2					= 21
+TAG_DEFINESHAPE2					= 22
+TAG_DEFINEBUTTONCXFORM				= 23
+TAG_PROTECT							= 24 #/* the author doesn't want the file to be opened */
+TAG_PATHSAREPOSTSCRIPT				= 25
+TAG_PLACEOBJECT2					= 26 #/* possibly onClipEvents inside */
+TAG_REMOVEOBJECT2					= 28
+TAG_SYNCFRAME						= 29
+TAG_FREEALL							= 31
+TAG_DEFINESHAPE3					= 32
+TAG_DEFINETEXT2						= 33
+TAG_DEFINEBUTTON2					= 34 #/* possibly button events inside */
+TAG_DEFINEBITSJPEG3					= 35
+TAG_DEFINEBITSLOSSLESS2				= 36
+TAG_DEFINEEDITTEXT					= 37
+TAG_DEFINEVIDEO						= 38
+TAG_DEFINEMOVIECLIP					= 39 #/* movie clip timeline comes */
+TAG_NAMECHARACTER					= 40
+TAG_SERIALNUMBER					= 41
+TAG_DEFINETEXTFORMAT				= 42
+TAG_FRAMELABEL						= 43
+TAG_SOUNDSTREAMHEAD2				= 45
+TAG_DEFINEMORPHSHAPE				= 46
+TAG_GENFRAME						= 47
+TAG_DEFINEFONT2						= 48
+TAG_GENCOMMAND						= 49
+TAG_DEFINECOMMANDOBJ				= 50
+TAG_CHARACTERSET					= 51
+TAG_FONTREF							= 52
+TAG_EXPORTASSETS					= 56
+TAG_IMPORTASSETS					= 57
+TAG_ENABLEDEBUGGER					= 58
+TAG_INITMOVIECLIP					= 59 #/* flash 6 mc initialization actions (#initclip .. #endinitclip) */
+TAG_DEFINEVIDEOSTREAM				= 60
+TAG_VIDEOFRAME						= 61
+TAG_DEFINEFONTINFO2					= 62
+TAG_DEBUGID							= 63
+TAG_ENABLEDEBUGGER2					= 64
+TAG_SCRIPTLIMITS					= 65
+TAG_SETTABINDEX						= 66
+TAG_DEFINESHAPE4					= 67
+TAG_FILEATTRIBUTES					= 69
+TAG_PLACEOBJECT3					= 70 #/* possibly onClipEvents inside */
+TAG_IMPORTASSETS2					= 71
+TAG_DEFINEFONTINFO3					= 73
+TAG_DEFINETEXTINFO					= 74
+TAG_DEFINEFONT3						= 75
+TAG_AVM2DECL						= 76
+TAG_METADATA						= 77
+TAG_SLICE9							= 78
+TAG_AVM2ACTION						= 82
+TAG_DEFINESHAPE5					= 83
+TAG_DEFINEMORPHSHAPE2				= 84
+TAG_DEFINEBITSPTR					= 1023
+import math
+from .bitsio import *
+from .dataio import *
+from .structure import *
+
+class SWFWriter(object):
+    def __init__(self, baseIO):
+        self.baseIO = baseIO
+        self.dataIO = DataWriter(self.baseIO)
+    
+    def writeRect(self, rect):
+        bits = BitWriter(self.baseIO)
+        
+        highestBit = lambda x: math.floor(math.log(x, 2)) if x != 0 else -1
+        
+        count = max(*map(highestBit, (rect.left, rect.right, rect.bottom, rect.top))) + 1
+        if not (0 < count < 2 ** 5 - 1):
+            raise "Frame size is too big or too small"
+        
+        bits.write(count, 5)
+        bits.write(rect.left, count)
+        bits.write(rect.right, count)
+        bits.write(rect.bottom, count)
+        bits.write(rect.top, count)
+        bits.flush()
+    
+    def writeHeader(self, header):
+        if header.compressed:
+            raise "Compression is not supported yet."
+        
+        self.dataIO.write(b"FWS")
+        self.dataIO.uli8(header.version)
+        # note that we write a zero length since the actual file size is not yet known
+        # wonder if it will cause any bugs..
+        self.dataIO.uli32(0)
+        self.writeRect(header.frameSize)
+        self.dataIO.uli16(header.frameRate)
+        self.dataIO.uli16(header.frameCount)
+    
+    def writeTag(self, tag):
+        length = len(tag.rawData)
+        tagCodeAndLength = (tag.type << 6) | (63 if length >= 63 else length)
+        self.dataIO.uli16(tagCodeAndLength)
+        
+        if length >= 63:
+            self.dataIO.uli32(length)
+        
+        self.dataIO.write(tag.rawData)
+        return tag
+    
+    def write(self, swf):
+        self.writeHeader(swf.header)
+        for tag in swf.tags:
+            self.writeTag(tag)
+        self.writeTag(Tag())