Skip to content

Instantly share code, notes, and snippets.

@tmshv
Created November 6, 2012 16:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tmshv/4025805 to your computer and use it in GitHub Desktop.
Save tmshv/4025805 to your computer and use it in GitHub Desktop.
SWF File Format Action Script Parser
package ru.gotoandstop.bytes{
import flash.utils.ByteArray;
/**
* @author: Roman Timashev (roman@tmshv.ru)
**/
public class ByteArrayUtil{
private static var __bitPosition:uint;
public function ByteArrayUtil(){
}
public static function readBitSequence(bytes:ByteArray, position:uint, bitLength:uint, length:uint, bitOffset:uint=0):Object{
if(!bytes){
throw new ArgumentError('Error #1507 Argument bytes connot be null.');
}
var result:Object = 0;
var pos:uint = bytes.position;
if(length){
bytes.position = position;
ByteArrayUtil.__bitPosition = bitOffset;
//var needed:uint = Math.ceil((bitLength * number) / 8);
//if((needed + bitOffset) > (bytes.length - bytes.position)){
// trace('needed:', needed, ', available:', bytes.length - bytes.position)
// return 0;
//}
if(length == 1){
result = ByteArrayUtil.readBits(bitLength, bytes);
}else{
result = new Array();
for(var i:uint; i < length; i++){
result.push(ByteArrayUtil.readBits(bitLength, bytes));
}
}
}
bytes.position = pos;
return result;
}
private static function readBits(n:uint, bytes:ByteArray):uint{
var result:uint;
var byte:uint = bytes[bytes.position + Math.floor(__bitPosition / 8)];
var localBitPosition:uint = __bitPosition % 8;
var a:uint = 8 - localBitPosition;
if(a > n){
result = byte >> (8 - n);
__bitPosition += n;
}else{
var upper:uint = ((byte << localBitPosition) & 0xFF) >> localBitPosition;
__bitPosition += a;
var lower:uint = readBits(n - a, bytes);
result = upper << (n - a) | lower;
}
return result;
}
public static function writeStringBytes(bytes:ByteArray, string:String):void{
trace("ByteArrayUtils.writeStringByte(): I'm shitty!")
var length:uint = string.length;
var byte:int;
for(var i:uint; i < length; i += 2){
byte = parseInt(string.substr(i, 2), 0x10);
bytes.writeByte(byte);
}
}
public static function findStringEnd(bytes:ByteArray, offset:uint=0):uint{
if(!bytes) throw new ArgumentError('Error #1507 Argument bytes connot be null.');
var end:uint;
var pos:uint = bytes.position;
bytes.position = offset;
while(bytes.bytesAvailable){
var zero:uint = bytes.readUnsignedByte();
if(!zero){
end = bytes.position;
break;
}
}
bytes.position = pos;
return end;
}
}
}
package ru.gotoandstop.bytes{
import flash.geom.Rectangle;
import flash.utils.ByteArray;
import flash.utils.Endian;
/**
* @author: Roman Timashev (roman@tmshv.ru)
* @version: 1.1
* @date: 01.08.2009
**/
public class SWFRipper extends ByteArray{
private static const SWF_SIGNATURE_UNCOMPRESSED:String = 'FWS';
private static const SWF_SIGNATURE_COMPRESSED:String = 'CWS';
private var _signature:String;
public function get signature():String{
return this._signature;
}
private var _version:uint;
public function get version():uint{
return this._version;
}
private var _length:uint;
public function get swfLength():uint{
return this._length;
}
private var _frameSize:Rectangle;
public function get frameSize():Rectangle{
return this._frameSize;
}
private var _frameRate:uint;
public function get frameRate():uint{
return this._frameRate;
}
private var _frameNumber:uint;
public function get frameNumber():uint{
return this._frameNumber;
}
private var _compressed:Boolean;
public function get compressed():Boolean{
return this._compressed;
}
private var __firstTagPosition:uint;
public function SWFRipper(bytes:ByteArray=null){
super();
if(bytes) this.parse(bytes);
}
public function parse(bytes:ByteArray):void{
bytes.position = 0;
var valid:Boolean = SWFRipper.isSWF(bytes);
if(valid) super.writeBytes(bytes);
else throw new ArgumentError('Error #1508: The value specifed for argument bytes is invalid.');
bytes.position = 0;
var sign:String = bytes.readUTFBytes(3);
if(sign == SWFRipper.SWF_SIGNATURE_COMPRESSED){
this.uncompress();
}else{
this._compressed = false;
}
this.parseHeader();
}
public override function uncompress():void{
var endian:String = super.endian;
super.endian = Endian.LITTLE_ENDIAN;
super.position = 0;
var sign:String = super.readUTFBytes(3);
if(sign == SWFRipper.SWF_SIGNATURE_COMPRESSED){
var uncompressed:ByteArray = new ByteArray();
uncompressed.writeUTFBytes(SWFRipper.SWF_SIGNATURE_UNCOMPRESSED);
uncompressed.writeBytes(this, 3, 5);
super.position = 8;
super.readBytes(this);
super.uncompress();
uncompressed.writeBytes(this);
super.length = 0;
super.writeBytes(uncompressed);
}else if(sign == SWFRipper.SWF_SIGNATURE_COMPRESSED){
}else{
throw new ArgumentError('Error #1508: The value specifed for argument bytes is invalid.');
}
this._compressed = false;
super.endian = endian;
super.position = 0;
}
public override function compress():void{
var endian:String = super.endian;
super.endian = Endian.LITTLE_ENDIAN;
super.position = 0;
var sign:String = super.readUTFBytes(3);
if(sign == SWFRipper.SWF_SIGNATURE_UNCOMPRESSED){
var compressed:ByteArray = new ByteArray();
compressed.writeUTFBytes(SWFRipper.SWF_SIGNATURE_COMPRESSED);
compressed.writeBytes(this, 3, 5);
super.position = 8;
super.readBytes(this);
super.compress();
compressed.writeBytes(this);
super.length = 0;
super.writeBytes(compressed);
}else if(sign == SWFRipper.SWF_SIGNATURE_COMPRESSED){
}else{
throw new ArgumentError('Error #1508: The value specifed for argument bytes is invalid.');
}
this._compressed = true;
super.endian = endian;
super.position = 0;
}
public function removeTags(swfTag:uint):void{
var endian:String = super.endian;
var position:uint = super.position;
super.endian = Endian.LITTLE_ENDIAN;
super.position = this.__firstTagPosition;
var result:ByteArray = new ByteArray();
var tag:uint;
var type:uint;
var length:uint;
var long:Boolean;
var pos:uint;
var bytes:ByteArray;
while(super.bytesAvailable){
pos = super.position;
tag = super.readUnsignedShort();
type = tag >> 6;
long = (tag & 0x3F) == 0x3F;
length = long ? super.readUnsignedInt() : (tag & 0x3F);
switch(type){
case swfTag:
result.writeBytes(this, 0, pos);
break;
}
super.position = super.position + (!long ? (length + 2) : (length + 6));
}
result.writeBytes(this, super.position);
super.length = 0;
super.writeBytes(result);
super.position = position;
super.endian = endian;
}
public function getTags(swfTag:uint):Array{
var result:Array = new Array();
var position:uint = super.position;
super.position = __firstTagPosition;
var endian:String = super.endian;
super.endian = Endian.LITTLE_ENDIAN;
var tag:uint;
var type:uint;
var length:uint;
var long:Boolean;
var pos:uint;
var bytes:ByteArray;
while(super.bytesAvailable){
pos = super.position;
tag = super.readUnsignedShort();
type = tag >> 6;
long = ((tag & 0x3F) == 0x3F);
length = long ? super.readUnsignedInt() : (tag & 0x3F);
switch(type){
case swfTag:
bytes = new ByteArray();
bytes.writeBytes(this, pos, (!long ? (length + 2) : (length + 6)));
bytes.position = 0;
result.push(bytes);
break;
}
super.position = super.position + length;
}
super.position = position;
super.endian = endian;
return result;
}
public function getTag(swfTag:uint, offset:uint=0):ByteArray{
var list:Array = this.getTags(swfTag);
var result:ByteArray = (offset < list.length) ? list[offset] as ByteArray : list[list.length - 1] as ByteArray;
return result;
}
public function getMetadata():XML{
var data:Array = this.getTags(SWFTag.METADATA);
var bytes:ByteArray = data[0] as ByteArray;
bytes.endian = Endian.LITTLE_ENDIAN;
var tag:uint = bytes.readUnsignedShort();
var length:uint = ((tag & 0x3F) == 0x3F) ? bytes.readUnsignedInt() : (tag & 0x3F);
var meta:String = bytes.readUTFBytes(length);
return new XML(meta);
}
private function parseHeader():void{
var endian:String = super.endian;
var pos:uint = super.position;
super.endian = Endian.LITTLE_ENDIAN;
super.position = 0;
this._signature = super.readUTFBytes(3);
this._version = super.readUnsignedByte();
this._length = super.readUnsignedInt();
var nBits:uint = ByteArrayUtil.readBitSequence(this, 8, 5, 1) as uint;
var rect:Array = ByteArrayUtil.readBitSequence(this, 8, nBits, 4, 5) as Array;
super.position = super.position + Math.ceil((5 + nBits * 4) / 8);
this._frameSize = new Rectangle();
this._frameSize.x = rect[0] / 20;
this._frameSize.y = rect[2] / 20;
this._frameSize.width = rect[1] / 20;
this._frameSize.height = rect[3] / 20;
this._frameRate = super.readUnsignedShort() >> 8;
this._frameNumber = super.readUnsignedShort();
__firstTagPosition = super.position;
super.position = pos;
}
private static function isSWF(bytes:ByteArray):Boolean{
var sign:String = bytes.readUTFBytes(3);
if(sign == SWFRipper.SWF_SIGNATURE_COMPRESSED || sign == SWFRipper.SWF_SIGNATURE_UNCOMPRESSED) return true;
else return false;
}
}
}
package ru.gotoandstop.bytes{
public class SWFTag{
public static const END:uint = 0;
public static const SHOW_FRAME:uint = 1;
public static const DEFINE_SHAPE:uint = 2;
public static const PLACE_OBJECT:uint = 4;
public static const REMOVE_OBJECT:uint = 5;
public static const DEFINE_BITS:uint = 6;
public static const DEFINE_BUTTON:uint = 7;
public static const JPEG_TABLES:uint = 8;
public static const SET_BACKGROUND_COLOR:uint = 9;
public static const DEFINE_FONT:uint = 10;
public static const DEFINE_TEXT:uint = 11;
public static const DO_ACTION:uint = 12;
public static const DEFINE_FONT_INFO:uint = 13;
public static const DEFINE_SOUND:uint = 14;
public static const START_SOUND:uint = 15;
public static const DEFINE_BUTTON_SOUND:uint = 17;
public static const SOUND_STREAM_HEAD:uint = 18;
public static const SOUND_STREAM_BLOCK:uint = 19;
public static const DEFINE_BITS_LOSSLESS:uint = 20;
public static const DEFINE_BITS_JPEG2:uint = 21;
public static const DEFINE_SHAPE2:uint = 22;
public static const DEFINE_BUTTON_CXFORM:uint = 23;
public static const PROTECT:uint = 24;
public static const PLACE_OBJECT2:uint = 26;
public static const REMOVE_OBJECT2:uint = 28;
public static const DEFINE_SHAPE3:uint = 32;
public static const DEFINE_TEXT2:uint = 33;
public static const DEFINE_BUTTON2:uint = 34;
public static const DEFINE_BITS_JPEG3:uint = 35;
public static const DEFINE_BITS_LOSSLESS2:uint = 36;
public static const DEFINE_EDIT_TEXT:uint = 37;
public static const DEFINE_SPRITE:uint = 39;
public static const FRAME_LABEL:uint = 43;
public static const SOUND_STREAM_HEAD2:uint = 45;
public static const DEFINE_MORPH_SHAPE:uint = 46;
public static const DEFINE_FONT2:uint = 48;
public static const EXPORT_ASSETS:uint = 56;
public static const IMPORT_ASSETS:uint = 57;
public static const ENABLE_DEBUGGER:uint = 58;
public static const DO_INIT_ACTION:uint = 59;
public static const DEFINE_VIDEO_STREAM:uint = 60;
public static const VIDE0_FRAME:uint = 61;
public static const DEFINE_FONT_INFO2:uint = 62;
public static const ENABLE_DEBUGGER2:uint = 64;
public static const SCRIPTS_LIMITS:uint = 65;
public static const SET_TAB_INDEX:uint = 66;
public static const FILE_ATTRIBUTES:uint = 69;
public static const PLACE_OBJECT3:uint = 70;
public static const IMPORT_ASSETS2:uint = 71;
public static const DEFINE_FONT_ALIGN_ZONES:uint = 73;
public static const CSM_TEXT_SETTINGS:uint = 74;
public static const DEFINE_FONT3:uint = 75;
public static const SYMBOL_CLASS:uint = 76;
public static const METADATA:uint = 77;
public static const DEFINE_SCALING_GRID:uint = 78;
public static const DO_ABC:uint = 82;
public static const DEFINE_SHAPE4:uint = 83;
public static const DEFINE_MORPH_SHAPE2:uint = 84;
public static const DEFINE_SCENE_AND_FRAME_LABEL_DATA:uint = 86;
public static const DEFINE_BINARY_DATA:uint = 87;
public static const DEFINE_FONT_NAME:uint = 88;
public static const START_SOUND2:uint = 89;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment