Skip to content

Instantly share code, notes, and snippets.

@imcotton
Created April 20, 2010 13:14
Show Gist options
  • Save imcotton/372417 to your computer and use it in GitHub Desktop.
Save imcotton/372417 to your computer and use it in GitHub Desktop.
lightweight SWF Tag reader
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