Created
April 20, 2010 13:14
-
-
Save imcotton/372417 to your computer and use it in GitHub Desktop.
lightweight SWF Tag reader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package | |
{ | |
import flash.display.Sprite; | |
import flash.utils.ByteArray; | |
import flash.utils.Endian; | |
[SWF(width="400", height="300", frameRate="50")] | |
public class SWFReader extends Sprite | |
{ | |
public function SWFReader () | |
{ | |
this.init(); | |
this.parse(); | |
this.print(); | |
} | |
private var source:ByteArray; | |
private var ssHeader:SSHeader; | |
private var ssTagFileAttribute:SSTagFileAttribute; | |
private var ssTagMetadata:SSTagMetadata; | |
private var ssTagProductInfo:SSTagProductInfo; | |
private var ssTagsDoABC:Array; | |
private function parse ():void | |
{ | |
this.ssHeader = new SSHeader(this.source); | |
this.ssTagsDoABC = []; | |
var tag:SSTagNull; | |
do | |
{ | |
tag = new SSTagNull(this.source); | |
switch (tag.header.type) | |
{ | |
case SSTagFileAttribute.TYPE: | |
this.ssTagFileAttribute = new SSTagFileAttribute(this.source); | |
break; | |
case SSTagMetadata.TYPE: | |
this.ssTagMetadata = new SSTagMetadata(this.source); | |
break; | |
case SSTagProductInfo.TYPE: | |
this.ssTagProductInfo = new SSTagProductInfo(this.source); | |
break; | |
case SSTagDoABC.TYPE: | |
this.ssTagsDoABC.push(new SSTagDoABC(this.source)); | |
break; | |
case SSTagEnd.TYPE: | |
break; | |
default: | |
tag.forward(); | |
} | |
} | |
while (tag.header.type != SSTagEnd.TYPE); | |
} | |
private function print ():void | |
{ | |
trace(this.ssHeader.frameSize); | |
if (this.ssTagProductInfo) | |
{ | |
trace(this.ssTagProductInfo.compileDate); | |
trace(this.ssTagProductInfo.buildNumber); | |
} | |
this.printDoABCTags(); | |
} | |
private function printDoABCTags ():void | |
{ | |
for each (var i:SSTagDoABC in this.ssTagsDoABC) | |
trace(i.name); | |
} | |
private function init ():void | |
{ | |
this.source = new ByteArray(); | |
this.source.endian = Endian.LITTLE_ENDIAN; | |
this.loaderInfo.bytes.readBytes(this.source); | |
} | |
} | |
} | |
import flash.geom.Rectangle; | |
import flash.utils.ByteArray; | |
class SSHeader | |
{ | |
public function SSHeader ($source:ByteArray) | |
{ | |
this.source = $source; | |
this.parse(); | |
} | |
private var source:ByteArray; | |
public var isCompressed:Boolean; | |
public var fileLength:uint; | |
public var version:uint; | |
public var frameSize:Rectangle; | |
public var frameRate:Number; | |
public var frameCount:uint; | |
private function parse ():void | |
{ | |
var signature:String = this.source.readUTFBytes(3); | |
if (!(/[CF]WS/.test(signature))) | |
throw new Error("invalid"); | |
this.version = this.source.readUnsignedByte(); | |
this.fileLength = this.source.readUnsignedInt(); | |
if (this.isCompressed = /C/.test(signature)) | |
this.uncompress(this.source); | |
this.frameSize = new SSDataRect(this.source).rect; | |
this.frameRate = this.source.readUnsignedShort() / 256; | |
this.frameCount = this.source.readUnsignedShort(); | |
} | |
private function uncompress ($byteArray:ByteArray):void | |
{ | |
var pos:uint = $byteArray.position; | |
var tmp:ByteArray = new ByteArray(); | |
$byteArray.readBytes(tmp); | |
tmp.position = 0; | |
tmp.uncompress(); | |
$byteArray.position = pos; | |
$byteArray.writeBytes(tmp); | |
$byteArray.position = pos; | |
} | |
} | |
class SSDataRect | |
{ | |
public function SSDataRect ($source:ByteArray) | |
{ | |
this.source = $source; | |
this.parse(); | |
} | |
private var source:ByteArray; | |
private var count:uint = 0; | |
public var rect:Rectangle; | |
private function parse ():void | |
{ | |
var nbits:uint = this.readBits(5); | |
this.rect = new Rectangle(); | |
this.rect.left = this.readBits(nbits) / 20; | |
this.rect.right = this.readBits(nbits) / 20; | |
this.rect.top = this.readBits(nbits) / 20; | |
this.rect.bottom = this.readBits(nbits) / 20; | |
} | |
private function readBits ($nbits:uint):int | |
{ | |
if (this.count > 0) | |
this.source.position--; | |
var totals:uint = this.count + $nbits; | |
var nbytes:uint = Math.ceil(totals / 8); | |
var next:uint = totals % 8; | |
var num:int = 0; | |
while (--nbytes) | |
{ | |
num <<= 8; | |
num += this.source.readUnsignedByte(); | |
if (this.count > 0) | |
{ | |
num &= (1 << (8 - this.count)) - 1; | |
this.count = 0; | |
} | |
} | |
this.count = next; | |
num <<= next; | |
num += this.source.readUnsignedByte() >> (8 - next); | |
return num; | |
} | |
} | |
class SSTagHeader | |
{ | |
public function SSTagHeader ($type:uint = 0, $length:uint = 0) | |
{ | |
this.type = $type; | |
this.length = $length; | |
} | |
public var type:uint; | |
public var length:uint; | |
public var isLong:Boolean; | |
public function get offset ():uint | |
{ | |
return this.isLong ? 6 : 2; | |
} | |
public function parse ($source:ByteArray):void | |
{ | |
if (!$source.bytesAvailable) | |
return; | |
var byte:uint = $source.readUnsignedShort(); | |
this.type = byte >> 6; | |
this.length = byte & 0x3F; | |
if (this.isLong = this.length == 0x3F) | |
this.length = $source.readInt(); | |
} | |
} | |
class SSTag | |
{ | |
public function SSTag ($source:ByteArray) | |
{ | |
if (Object(this).constructor == SSTag) | |
throw new Error("abs") | |
this.source = $source; | |
this.parse(); | |
} | |
protected var source:ByteArray; | |
public var header:SSTagHeader; | |
private function parse ():void | |
{ | |
this.header = new SSTagHeader(); | |
this.header.parse(this.source); | |
} | |
} | |
class SSTagNull extends SSTag | |
{ | |
public function SSTagNull ($source:ByteArray) | |
{ | |
super($source); | |
this.source.position -= this.header.offset; | |
} | |
public function forward ():void | |
{ | |
this.source.position += this.header.length + this.header.offset; | |
} | |
} | |
class SSTagMetadata extends SSTag | |
{ | |
public static const TYPE:uint = 77; | |
public function SSTagMetadata ($source:ByteArray) | |
{ | |
super($source); | |
this.parse(); | |
} | |
public var xml:XML; | |
private function parse ():void | |
{ | |
this.xml = XML(this.source.readMultiByte(this.header.length, "utf-8")); | |
} | |
} | |
class SSTagProductInfo extends SSTag | |
{ | |
public static const TYPE:uint = 41; | |
public function SSTagProductInfo ($source:ByteArray) | |
{ | |
super($source); | |
this.parse(); | |
} | |
public var productID:uint; | |
public var editionID:uint; | |
public var majorVersion:uint; | |
public var minorVersion:uint; | |
public var build:Number; | |
public var compileDate:Date; | |
public var buildNumber:String; | |
private function parse ():void | |
{ | |
this.productID = this.source.readUnsignedInt(); | |
this.editionID = this.source.readUnsignedInt(); | |
this.majorVersion = this.source.readUnsignedByte(), | |
this.minorVersion = this.source.readUnsignedByte(), | |
this.build = this.source.readUnsignedInt() | |
+ this.source.readUnsignedInt() * 0x100000000; | |
var ms:Number = this.source.readUnsignedInt() | |
+ this.source.readUnsignedInt() * 0x100000000; | |
this.compileDate = new Date(ms); | |
this.buildNumber = this.majorVersion | |
+ "." | |
+ this.minorVersion | |
+ " r" | |
+ this.build; | |
} | |
} | |
class SSTagFileAttribute extends SSTag | |
{ | |
public static const TYPE:uint = 69; | |
public function SSTagFileAttribute ($source:ByteArray) | |
{ | |
super($source); | |
this.parse(); | |
} | |
public var useDirectBlit:Boolean; | |
public var useGPU:Boolean; | |
public var hasMetadata:Boolean; | |
public var isActionScript3:Boolean; | |
public var useNetwork:Boolean; | |
private function parse ():void | |
{ | |
var bytes:uint = this.source.readUnsignedByte(); | |
this.useDirectBlit = !!(bytes & 0x40); | |
this.useGPU = !!(bytes & 0x20); | |
this.hasMetadata = !!(bytes & 0x10); | |
this.isActionScript3 = !!(bytes & 0x08); | |
this.useNetwork = !!(bytes & 0x01); | |
this.source.position += 3; | |
} | |
} | |
class SSDataString | |
{ | |
public function SSDataString ($source:ByteArray) | |
{ | |
this.source = $source; | |
this.parse(); | |
} | |
private var source:ByteArray; | |
public var string:String; | |
private function parse ():void | |
{ | |
var index:int = this.source.position; | |
while (this.source[index++]) | |
void; | |
this.string = this.source.readUTFBytes(index - this.source.position); | |
} | |
} | |
class SSTagDoABC extends SSTag | |
{ | |
public static const TYPE:uint = 82; | |
public function SSTagDoABC ($source:ByteArray) | |
{ | |
super($source); | |
this.parse(); | |
} | |
public var isLazyInitialize:Boolean; | |
public var name:String; | |
public var abcBytes:ByteArray; | |
private function parse ():void | |
{ | |
var offset:int = -this.source.position; | |
this.isLazyInitialize = !!(this.source.readUnsignedInt() & 0x01); | |
this.name = new SSDataString(this.source).string; | |
offset += this.source.position; | |
this.source.readBytes | |
( | |
this.abcBytes = new ByteArray(), | |
this.source.position, | |
this.header.length - offset | |
); | |
} | |
} | |
class SSTagEnd extends SSTag | |
{ | |
public static const TYPE:uint = 0; | |
public function SSTagEnd ($source:ByteArray) | |
{ | |
super($source); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment