SWF Analyzer

by hycro
やっつけSWF解析
♥0 | Line 330 | Modified 2010-07-08 01:50:12 | MIT License
play

ActionScript3 source code

/**
 * Copyright hycro ( http://wonderfl.net/user/hycro )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/tcWK
 */

// やっつけSWF解析
package {
    import com.bit101.components.PushButton;
    import com.bit101.components.TextArea;
    
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.net.FileFilter;
    import flash.net.FileReference;
    import flash.utils.ByteArray;
    
    [SWF(frameRate="30", width="465", height="465", backgroundColor="0xFFFFFF")]
    public class SWFAnalyzerTest extends Sprite {
        
        private var _ta:TextArea;
        private var _fileRef:FileReference;
        
        public function SWFAnalyzerTest() {
            this.addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(evt:Event=null):void {
            new PushButton(this, 5, 5, "load", selectFile);
            
            _ta = new TextArea(this, 0, 30);
            _ta.width = stage.stageWidth;
            _ta.height = stage.stageHeight - 30;
        }
        
        private function selectFile(evt:Event):void {
            _fileRef = new FileReference();
            _fileRef.addEventListener(Event.SELECT, onSelect);
            _fileRef.addEventListener(Event.COMPLETE, onComplete);
            _fileRef.browse([new FileFilter("SWF", "*.swf")]);
        }
        
        private function onSelect(evt:Event):void {
            _fileRef.load();
        }
        
        private function onComplete(evt:Event):void {
            var analyzer:SWFAnalyzer = new SWFAnalyzer(_fileRef.data);
            
            try {
                analyzer.analyze();
            } catch (err:Error) {
                _ta.text = err.toString();
                return;
            }
            
            _ta.text = "[header]\n"
            _ta.text += "compressed: " + analyzer.compressed + "\n";
            _ta.text += "version: " + analyzer.version + "\n";
            _ta.text += "filesize: " + analyzer.filesize + "\n";
            _ta.text += "stageRect: " + analyzer.stageRect + "\n";
            _ta.text += "frameRate: " + analyzer.frameRate + "\n";
            _ta.text += "numFrames: " + analyzer.numFrames + "\n";
            _ta.text += "\n[tags]\n";
            for each (var t:Tag in analyzer.tags) {
                _ta.text += t.toString() + "\n";
            }
        }
    }
}


import flash.geom.Rectangle;
import flash.utils.ByteArray;
import flash.utils.Endian;

class SWFAnalyzer {
    // header
    public var compressed:Boolean;
    public var version:uint;
    public var filesize:uint;
    
    // header_movie
    public var stageRect:Rectangle;
    public var frameRate:Number;
    public var numFrames:Number;
    
    // タグ
    public var tags:Vector.<Tag>;
    
    // input
    private var _data:ByteArray;
    
    
    public function SWFAnalyzer(data:ByteArray) {
        _data = data;
        _data.endian = Endian.LITTLE_ENDIAN;
        tags = new Vector.<Tag>();
    }
    
    public function analyze():void {
        readHeader();
        if (compressed) {
            _data = uncompless(_data, 8);
            _data.position = 8;
        }
        readHeader2();
        readTags();
    }
    
    
    // header
    private function readHeader():void {
        // 圧縮の有無(3byte)
        var cmp:String = _data.readUTFBytes(3);
        if (cmp == "FWS") {
            this.compressed = false;
        } else if (cmp == "CWS") {
            this.compressed = true;
        } else {
            throw new Error();
        }
        
        // SWFのバージョン(1byte)
        this.version = _data.readUnsignedByte();
        
        // ファイルサイズ(4byte)
        this.filesize = _data.readUnsignedInt();
    }
    
    
    // header2
    private function readHeader2():void {
        var bitReader:BitReader = new BitReader(_data);
        
        // stageRect
        var len:uint = bitReader.readUnsignedInt(5);
        var x:uint = bitReader.readUnsignedInt(len);
        var w:uint = bitReader.readUnsignedInt(len);
        var y:uint = bitReader.readUnsignedInt(len);
        var h:uint = bitReader.readUnsignedInt(len);
        stageRect = new Rectangle(x/20, y/20, w/20, h/20);
        
        // frameRate
        var f:uint = _data.readUnsignedByte();
        var d:uint = _data.readUnsignedByte();
        frameRate = d + (f/256);
        
        // numFrames
        numFrames = _data.readUnsignedShort();
    }
    
    // Tag
    private function readTags():void {
        
        while (_data.bytesAvailable) {
            var tag:Tag = new Tag();
            
            var typeAndLength:uint = _data.readUnsignedShort();
            
            tag.type = (typeAndLength & 0xFFC0) >> 6;
            tag.length = (typeAndLength & 0x3F);
            
            if (tag.length == 0x3F) {
                tag.length = _data.readUnsignedInt();
            }
            
            if (tag.length) {
                _data.readBytes(tag.contents, 0, tag.length);
            }
            
            tags.push(tag);
        }
    }
    
    // zlib解凍
    private function uncompless(ba:ByteArray, offset:uint):ByteArray {
        var rv:ByteArray = new ByteArray();
        var tmp:ByteArray = new ByteArray();
        rv.endian = ba.endian;
        tmp.endian = ba.endian;
        
        // offset 以降をコピーしてzlib解凍
        ba.position = offset;
        ba.readBytes(tmp);
        tmp.uncompress();
        
        if (offset == 0) {
            return tmp;
        }
        
        // offset 以前と zlib解凍した内容をつなげる
        ba.position = 0;
        ba.readBytes(rv, 0, offset);
        tmp.readBytes(rv, offset);
        
        return rv;
    }
    
    private function dump(length:uint=100):void {
        var pos:uint = _data.position;
        var count:uint = 0;
        
        while (_data.bytesAvailable) {
            var str:String = "";
            for (var i:uint = 0; i < 16 && _data.bytesAvailable; i++) {
                var b:uint = _data.readUnsignedByte();
                for (var j:uint = 0; j < (8-b.toString(2).length); j++) {
                    str += "0";
                }
                str += b.toString(2);
                str += " ";
                
                if (++count >= length) {
                    trace(str);
                    _data.position = pos;
                    return;
                }
            }
            trace(str);
        }
        
        _data.position = pos;
    }
}

class Tag {
    public var type:uint;
    public var length:uint;
    public var contents:ByteArray;
    
    public function Tag() {
        contents = new ByteArray();
    }
    
    public function toString():String {
        switch (type) {
            case 0:
                return "End";
            case 1:
                return "ShowFrame";
            case 2:
                return "DefineShape";
            case 4:
                return "PlaceObject";
            case 5:
                return "RemoveObject";
            case 6:
                return "DefineBits";
            case 7:
                return "DefineButton";
            case 8:
                return "JPEGTables";
            case 9:
                return "SetBackgroundColor";
            case 10:
                return "DefineFont"
            case 11:
                return "DefineText"
            case 12:
                return "DoAction"
            case 13:
                return "DefineFontInfo"
            case 14:
                return "DefineSound"
            case 15:
                return "StartSound"
            case 17:
                return "DefineButtonSound"
            case 18:
                return "SoundStreamHead"
            case 19:
                return "SoundStreamBlock"
            case 20:
                return "DefineBitsLossless"
            case 21:
                return "DefineBitsJPEG2"
            case 22:
                return "DefineShape2"
            case 23:
                return "DefineButtonCxform"
            case 24:
                return "Protect"
            case 26:
                return "PlaceObject2"
            case 28:
                return "RemoveObject2"
            case 32:
                return "DefineShape3"
            case 33:
                return "DefineText2"
            case 34:
                return "DefineButton2"
            case 35:
                return "DefineBitsJPEG3"
            case 36:
                return "DefineBitsLossless2"
            case 37:
                return "DefineEditText"
            case 39:
                return "DefineSprite"
            case 43:
                return "FrameLabel"
            case 45:
                return "SoundStreamHead2"
            case 46:
                return "DefineMorphShape"
            case 48:
                return "DefineFont2"
            case 56:
                return "ExportAssets"
            case 57:
                return "ImportAssets"
            case 58:
                return "EnableDebugger"
            case 59:
                return "DoInitAction"
            case 60:
                return "DefineVideoStream"
            case 61:
                return "VideoFrame"
            case 62:
                return "DefineFontInfo2"
            case 64:
                return "EnableDebugger2"
            case 65:
                return "ScriptLimits"
            case 66:
                return "SetTabIndex"
            case 69:
                return "FileAttributes"
            case 70:
                return "PlaceObject3"
            case 71:
                return "ImportAssets2"
            case 73:
                return "DefineFontAlignZones"
            case 74:
                return "CSMTextSettings"
            case 75:
                return "DefineFont3"
            case 76:
                return "SymbolClass"
            case 77:
                return "Metadata"
            case 78:
                return "DefineScalingGrid"
            case 82:
                return "DoABC"
            case 83:
                return "DefineShape4"
            case 84:
                return "DefineMorphShape2"
            case 86:
                return "DefineSceneAndFrameLabelData"
            case 87:
                return "DefineBinaryData"
            case 88:
                return "DefineFontName"
            case 89:
                return "StartSound2"
            case 90:
                return "DefineBitsJPEG4"
            case 91:
                return "DefineFont4"
        }
        return "undefined? (" + type + ")";
    }
}

// ByteArrayをbit単位で読むためのクラス
class BitReader {
    private static const ONE:Boolean = true;
    private static const ZERO:Boolean = false;
    
    private var _input:ByteArray;
    private var _currByte:uint;
    private var _mask:uint;
    
    public function BitReader(input:ByteArray) {
        _input = input;
        _currByte = NaN;
        _mask = 0;
    }
    
    // bit単位でデータを読みます
    public function readUnsignedInt(length:uint=0):uint {
        var rv:uint = 0;
        
        length = Math.min(length, 32);
        
        for (var i:int = length-1; i >= 0; i--) {
            if (next() == ONE) {
                rv += Math.pow(2, i);
            }
        }
        
        return rv;
    }
    
    // 次のビットを取得します
    private function next():Boolean {
        if ((_mask >>= 1) == 0) {
            _currByte = _input.readUnsignedByte();
            _mask = 0x80;
        }
        
        return (_currByte & _mask) ? ONE : ZERO;
    }
    
    // 残りのビットを切り捨てます
    public function floor():void {
        _mask = 0;
    }
}

Forked