forked from: WonderFLCompiler

by yasurageruheya forked from WonderFLCompiler (diff: 1)
♥0 | Line 797 | Modified 2015-12-17 11:21:17 | MIT License
play

ActionScript3 source code

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

// forked from Glidias's WonderFLCompiler
package 
{
    import com.bit101.components.CheckBox;
    import com.bit101.components.HBox;
    import com.bit101.components.InputText;
    import com.bit101.components.Label;
    import com.bit101.components.PushButton;
    import com.bit101.components.TextArea;
    import com.bit101.components.VBox;
    import com.bit101.components.Window;
    import flash.display.DisplayObject;
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.IEventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.KeyboardEvent;
    import flash.net.FileFilter;
    import flash.net.FileReference;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.ui.Keyboard;
    import flash.utils.ByteArray;
    import flash.utils.Dictionary;
    import flash.utils.setTimeout;
    /**
     * Compiles everything from a .swf into a single .as file, given a source code directory!  WIP...
     * @author Glenn Ko
     */
    public class WonderFLCompiler extends Sprite
    {
        private var btnOutput:PushButton;
        private var btnExcludes:PushButton;
        private var btnIncludes:PushButton;
        private var btnLoad:PushButton;
        
        private var pages:Vector.<TextArea> = new Vector.<TextArea>(4,true);
        private var pageHolder:Window;
        private var fileRef:FileReference;
        private var _includedClasses:Array;
        private var _loadQueue:Array;

        
        // eg. (to exclude/include a specific class in a package) com.bit101::SomeSpecialClass    
        // or  (to exclude/include an entire package) com.bit101.**  
        // or  (to exclude/include a specific package folder only) com.bit101.somepackage.*
        private static const EXCLUDES:Array = [   //
            "flash.**", "private.**", "mx.**", "com.bit101.**", "alternativa.**", "jp.nium.**", "jp.progression.**"
        ]
        
        private static const FIELD_DOC_CLASS:String = ""; //"WonderFLIsles";
        private static const FIELD_SRC_PATH:String = ""; //"https://raw.github.com/Glidias/Asharena/master/src/";
        private var fieldSrcPath:InputText;
        private var fieldDoc:InputText;
        
        
        
        // STUFF TO RESET ON Every loading of new swf
        private var stripClasses:Array = [];
        private var stripPackages:Array = [];
        private var _failedClasses:Array = [];
        private var outputData:String = "";
        private var loaderDict:Dictionary = new Dictionary();
        private var _docClassPath:String;
        private var dataList:Array = [];// = new Dictionary();
        
        
        
        private var excludeLoadPackages:Array = EXCLUDES;
        private var _excludePackageless:CheckBox;
        
        static public const CARRIAGE_RETURN:String = String.fromCharCode(13);
        static public const TXT_ATTEMPT_TO_LOAD:String = "Classes to attempt to load:";
        
        
        public function WonderFLCompiler() 
        {
            var vBox:VBox = new VBox(this);
            var hBox:HBox = new HBox(vBox); 
            btnLoad = new PushButton(hBox, 0, 0, "Load SWF file...", onBtnClick);
            btnIncludes = new PushButton(hBox, 0, 0, "Load Includes >", onBtnClick);
            btnExcludes = new PushButton(hBox, 0, 0, "Load Excludes >", onBtnClick);
            btnOutput = new PushButton(hBox, 0, 0, "View Output >", onBtnClick);
            
            hBox = new HBox(vBox);
            new Label(hBox, 0, 0, "Document class");
            fieldDoc = new InputText(hBox, 0, 0, FIELD_DOC_CLASS);
            new Label(hBox, 0, 0, "Source directory");
            fieldSrcPath = new InputText(hBox, 0, 0, FIELD_SRC_PATH);
            
            _excludePackageless =  new CheckBox(hBox, 0, 0, "X non-'./::'");
            
            fileRef = new FileReference();
            fileRef.addEventListener(Event.SELECT, onFileSelected);
            fileRef.addEventListener(Event.COMPLETE, onFileLoaded);
            
            var len:int = pages.length;
            for (var i:int = 0; i < len; i++) {
                pages[i] = getInputTextArea();
            }
            
            
            
            pages[2].text = EXCLUDES.join(CARRIAGE_RETURN);
            
            pageHolder = new Window(vBox, 0, 0, "");
            pageHolder.width = 400;
            pageHolder.height = 370;
            showPage(0);
            
            setTimeout( vBox.draw, 100);
            
        }
        
        private function popLoad():void {
            if (_loadQueue.length == 0) {
                
                doLoadCompletion();
                return;
            }
            var classPath:String = _loadQueue.pop();
            classPath = classPath.replace("::", ".");
            
            
            var urlLoader:URLLoader = new URLLoader();
            loaderDict[urlLoader] = classPath;
            urlLoader.addEventListener(Event.COMPLETE, onURLLoadComplete);
            urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onURLLoadError);
            //throw new Error( fieldSrcPath.text + classPath.split(".").join("/") );
            urlLoader.load( new URLRequest(fieldSrcPath.text+classPath.split(".").join("/")+".as" ) );
        }
        
        private function doLoadCompletion():void 
        {
            startDataProcessing();
        }
        
        private function startDataProcessing():void 
        {
        //    pageHolder.title = TXT_ATTEMPT_TO_LOAD + " ( Start processing: " + ( _failedClasses.length > 0 ? "with failed loads..." : "No failed loads!") + " )";
            var len:int = dataList.length;
            outputData += processData( dataList[0], false );
            for (var i:int = 1; i < len; i++) {
                outputData += processData( dataList[i], true );
            }
            finishDataProcessing();
        }
        
        private function finishDataProcessing():void 
        {
            pageHolder.title = TXT_ATTEMPT_TO_LOAD + " ( Done! " + ( _failedClasses.length > 0 ? "with failed loads..." : "No failed loads!") + " )";
            
            if (_failedClasses.length > 0) {
                pages[0].text = "Failed:\n_________\n" + _failedClasses.join(CARRIAGE_RETURN) + "\n\n______________________________________\n\n" + pages[0].text;
            }
            //outputData  = 
            pages[3].text = outputData;
        }
        
        private function findBraceIndexFromAbove(arr:Array, pattern:RegExp):int {
            var len:int = arr.length;
            for (var i:int = 0; i < len; i++) {
                if (arr[i].match(pattern)) return i;
            }
            return -1;
        }
        
        private function findBraceIndexFromBelow(arr:Array, pattern:RegExp):int {
            
            var i:int = arr.length;
            while(--i > -1) {
                if (arr[i].match(pattern)) return i;
            }
            return -1;
        }
        
        private function onURLLoadError(e:Event):void {
            (e.currentTarget as IEventDispatcher).removeEventListener(e.type, onURLLoadError);
            (e.currentTarget as IEventDispatcher).removeEventListener(Event.COMPLETE, onURLLoadComplete);
            
            var classPath:String = loaderDict[e.currentTarget];
            delete loaderDict[e.currentTarget];
            _failedClasses.push(classPath);
            
        
            
            
            popLoad();
        }

        
        private function onURLLoadComplete(e:Event):void 
        {
            (e.currentTarget as IEventDispatcher).removeEventListener(e.type, onURLLoadComplete);
            (e.currentTarget as IEventDispatcher).removeEventListener(IOErrorEvent.IO_ERROR, onURLLoadError);
            
            var data:String =  (e.currentTarget.data);
            var classPath:String = loaderDict[e.currentTarget];
            delete loaderDict[e.currentTarget];
            
            
            stripClasses.push(classPath);
            
            dataList.push(data);
            
            
            popLoad();
            
        }
        
        private function countOpeningBraces(str:String):int {

            var len:int = str.length;
            var count:int = 0;
            for (var i:int = 0; i < len; i++ ) {
                var char:String = str.charAt(i);
                if (char === "}") return count;
                count += char != "{" ? 0 : 1;
            }
            return count;
        }
        
        private function countOpeningBraces2(str:String):int {
            var count:int = 0;
            var flagCount:int = 0;
            var closeCount:int = 0;
            var len:int = str.length;
    
            for (var i:int = 0; i < len; i++ ) {
                var char:String = str.charAt(i);
            
                if (char === "{" ) {
                    flagCount++;
                    count++;
                }
                else if (char === "}" ) {
                    closeCount++;
                    count--;
                }
                if (flagCount != 0 && count == 0) {
                    if (closeCount != flagCount) throw new Error("Mismatch count of opening and close braces");
                    return flagCount;
                }
            }
            if (closeCount != flagCount) throw new Error("Mismatch count of opening and close braces");
            return flagCount;
        }
        
        private function processData(data:String, stripPackage:Boolean):String {
            var closingBraces:Array;
            var index:int;
            var openingBraces:Array;
            var str:String;
            
            // strip comments
            data = data.replace(/(\/\*([\s\S]*?)\*\/)|(\/\/(.*)$)/gm, "");
            
            //    data += " // EOF";
            
        //    var fullPathAppend:String = "[^;\\n]+[;\\n]";
            var regex:RegExp;
            for each(str in stripPackages) {   
                regex =  new RegExp("\\bimport\\s+"+str + ".", "g");
                data = data.replace(regex, "//import "+str+".");
            }
            // strip class imports  (these are classes found loaded in the directory)
            for each(str in stripClasses) {
                regex =  new RegExp("\\bimport\\s" + str + "[^$][;\\n]", "g");
                
                data = data.replace(regex, "//import " + str + ";\n");
            }
            

            // Handle package brace
            if (stripPackage) {     // is NOT document class
                openingBraces = data.split("{");
                var packageRegex:RegExp = /\bpackage\b[^{]+{/;
                index = findBraceIndexFromAbove(openingBraces, packageRegex);
                
                var packageBraceOffset:int = openingBraces.length - countOpeningBraces2(data);
            
                closingBraces = data.split("}");    

                closingBraces.splice(closingBraces.length - 2 - index - packageBraceOffset, 1, "\n//"); // [closingBraces.length - 1 - index];
                
                
                data = closingBraces.join("}");
                data = data.replace(packageRegex, "//package {");
                
                // remove public identifier for class
                data = data.replace(/\bpublic\b[^]+\bclass\b/, "/*public*/ class");
            }
            else {           // is document class
                data = data.replace(/\bpackage\b[^{]+{/, "package {");
            }
            
            // remove public identifier for interface
            data = data.replace(/\bpublic\s+interface\b/, "/*public*/ interface");
            
            
            
            // Further post processing ( like strip comments again)
            //data = data.replace(/(\/\*([\s\S]*?)\*\/)|(\/\/(.*)$)/gm, "");
            
            
            return data;
        }
        
        private function updateBoxContents():void 
        {
            pages[0].textField.scrollV = pages[0].textField.maxScrollV;
            pages[0].textField.dispatchEvent( new Event(Event.SCROLL) );
            pages[0].draw();
        }
        
        private function onFileLoaded(e:Event):void 
        {

            stripClasses = [];
            stripPackages = [];
            _failedClasses = [];
            outputData = "";
    
            loaderDict = new Dictionary();
            dataList = [];
            
            _includedClasses = getDefinitionNames(fileRef.data);
            cleanupIncludedClasses();
            pages[0].text = _includedClasses.join(CARRIAGE_RETURN);
            
            
            var docClass:String = fieldDoc.text;
            var srcPath:String = fieldSrcPath.text;
            
            /*
            if (!srcPath || !docClass) {
                pageHolder.title = TXT_ATTEMPT_TO_LOAD + " (Please specify Document Class and Source Path!)";
                return;
            }
            */
            
            
            
            
            //if (_loadQueue.length) {
             pageHolder.title = TXT_ATTEMPT_TO_LOAD + " (Press F2 to confirm start load!)";
             stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
            //}
            //else pageHolder.title = TXT_ATTEMPT_TO_LOAD + " (No classes found to load!)";
            
            
        }
        
        private function onKeyDown(e:KeyboardEvent):void 
        {
            
            if (e.keyCode === Keyboard.F2) {
                startLoading();
            }
        }
        
        private function startLoading():void 
        {
            var srcPath:String = fieldSrcPath.text;
                    var docClass:String = fieldDoc.text;
                    
            if (!srcPath || !docClass) {
                pageHolder.title = TXT_ATTEMPT_TO_LOAD + " (Please specify Document Class and Source Path!)";
                return;
            }
            
            
            _docClassPath = docClass.replace("::", ".");
            
             _includedClasses =  pages[0].text.split(CARRIAGE_RETURN);
             
             var docIndex:int;
             if ( (docIndex=_includedClasses.indexOf(docClass)) < 0) {
                 pageHolder.title = TXT_ATTEMPT_TO_LOAD + " (Could not find Document Class in load list!)";
                 return;
             }
            
             
             
            _loadQueue = _includedClasses.slice();
            _loadQueue.splice(docIndex, 1);
            _loadQueue.push(docClass);
            
            pageHolder.title = TXT_ATTEMPT_TO_LOAD + " (Loading...please wait!)";
            stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
                popLoad();
                
        }
        
        private function cleanupIncludedClasses():void 
        {
            var len:int = _includedClasses.length;
            var filters:Array = getExcludeFilters();
            var f:Array;
            var arr:Array = [];
            var myIncludedClasses:Array = pages[1].text.split(CARRIAGE_RETURN);
            for each(var includedStr:String in myIncludedClasses) {
                var tail:String = includedStr.split(".").pop();
                if (tail === "*" ) stripClasses.push(includedStr)
                else if (tail === "**") stripPackages.push(includedStr);
            }
            var fi:int;
            var fValue:String;
            var doContinue:Boolean = false;
            var fSplit:Array;
            var stripPackageless:Boolean  = _excludePackageless.selected;
            var docClass:String = fieldDoc.text;
            
            
            for (var i:int = 0; i < len; i++) {
                var candidate:String = _includedClasses[i];
                // enforce any specific included classes
                fi = myIncludedClasses.length;
                
                doContinue = false;
                while (--fi > -1) {
                    if (myIncludedClasses[fi] === candidate) {
                      if (!stripPackageless || candidate.indexOf("::") >=0 || candidate.indexOf(".") >= 0  || candidate === docClass )    arr.push(candidate);
                        doContinue = true;
                        break;
                    }
                }
                if (doContinue) continue;
                
                // filter away by excluded classes
                f = filters[0];
                fi = f.length;
                while (--fi > -1) {
                    if (f[fi] === candidate) {
                        doContinue = true;
                        break;
                    }
                }
                if (doContinue) continue;
                
                // filter away by excluded package folder
                f = filters[1];
                fi = f.length;
                while (--fi > -1) {
                    fValue = f[fi];
                    if (fValue === candidate.split(":").shift() )    {
                        doContinue = true;
                        break;
                    }
                }
                if (doContinue) continue;
                
                // filter away by excluded entire package
                f = filters[2];
                fi = f.length;
                
                while (--fi > -1) {
                    fValue = f[fi];
                    
                    if ( candidate.substr(0, fValue.length) === fValue) {
                        doContinue = true;
                        break;
                    }
                }
                if (doContinue) continue;
                
               if (!stripPackageless || candidate.indexOf("::") >=0 || candidate.indexOf(".") >= 0  || candidate === docClass )   arr.push(candidate);
            }
            
            _includedClasses = arr;
        }
        
        private function getExcludeFilters():Array 
        {
            var excludes:Array = pages[2].text.split(CARRIAGE_RETURN);
            
            
            var excludedClasses:Array  = [];
            var excludedPackageFolders:Array = [];
            var excludedPackages:Array = [];
            
            var len:int = excludes.length;
            
            for (var i:int = 0; i < len; i++) {
                var str:String = excludes[i];
                if (str.charAt(str.length - 1) === "*") {
                    if (str.charAt(str.length -2) === "*") {
            
                        excludedPackages.push(str.slice(0,str.length-3));  // slice away dot behind * as well
                    }
                    else {
                        excludedPackageFolders.push(str.slice(0,str.length-2));
                    }
                }
                else {
                    if (str.indexOf(":") < 0) {
                        var packageSplit:Array = str.split(".");
                        var className:String = packageSplit.pop();
                        str = packageSplit + ":" + className;
                    }
                    excludedClasses.push(str);
                }
            }

            return [excludedClasses, excludedPackageFolders, excludedPackages];
        }

        
        private function onFileSelected(e:Event):void 
        {
            stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
            fileRef.load();
        }
        
        private function getInputTextArea():TextArea 
        {
            var result:TextArea = new TextArea(null, 0, 0, "");
            result.width  = 400;
            result.height = 330;
            return result;
        }
        
        public function showPage(index:int):void {
            
        if (pageHolder.content.numChildren) pageHolder.content.removeChildAt(0);
        var child:DisplayObject =     pageHolder.addChild(pages[index]);
        child.y = 20;
        //child.y = 0;    
            if (index === 0) {
                pageHolder.title = TXT_ATTEMPT_TO_LOAD;
            }
            else if (index === 1) {
                pageHolder.title = "My Includes:";
            }
            else if (index === 2) {
                pageHolder.title = "My Excludes:";
            }
            else {
                pageHolder.title = "Output:";
            }
            pageHolder.draw();
        }
        
        private function onBtnClick(e:Event):void 
        {
            var targ:Object = e.currentTarget;
            if (targ === btnLoad) {
                loadSWFFile();
                showPage(0);
            }
            else if (targ === btnIncludes) {
                showPage(1);
            }
            else if (targ === btnExcludes) {
                showPage(2);
            }
            else {
                showPage(3);
            }
        }
        
        private function loadSWFFile():void 
        {
            
            fileRef.browse([new FileFilter("SWF file", "*.swf")]);
        }
        
      
  

    public function getDefinitionNames(data:Object, extended:Boolean = false, linkedOnly:Boolean = false):Array {
        var bytes:ByteArray;
        
        if (data is LoaderInfo) {
            bytes = (data as LoaderInfo).bytes;
        } else if (data is ByteArray) {
            bytes = data as ByteArray;
        } else throw new ArgumentError('Error #1001: The specified data is invalid');
        
        var position:uint = bytes.position;
        var finder:Finder = new Finder(bytes);
        bytes.position = position;
        return finder.getDefinitionNames(extended, linkedOnly);
    }
    
     }

}






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

/**
 * @private
 */
 class Finder {
        
    public function Finder(bytes:ByteArray) {
        super();
        this._data = new SWFByteArray(bytes);
    }

    /**
     * @private
     */
    private var _data:SWFByteArray;
    
    /**
     * @private
     */
    private var _stringTable:Array;
    
    /**
     * @private
     */
    private var _namespaceTable:Array;

    /**
     * @private
     */
    private var _multinameTable:Array;
            
    public function getDefinitionNames(extended:Boolean, linkedOnly:Boolean):Array {
        var definitions:Array = new Array();
        var tag:uint;
        var id:uint;
        var length:uint;
        var minorVersion:uint;
        var majorVersion:uint;
        var position:uint;
        var name:String;
        var index:int;
        
        while (this._data.bytesAvailable) {
            tag = this._data.readUnsignedShort();
            id = tag >> 6;
            length = tag & 0x3F;
            length = (length == 0x3F) ? this._data.readUnsignedInt() : length;
            position = this._data.position;
            
            if (linkedOnly) {
                if (id == 76) {
                    var count:uint = this._data.readUnsignedShort();
                    
                    while (count--) {
                        this._data.readUnsignedShort(); // Object ID
                        name = this._data.readString();
                        index = name.lastIndexOf('.');
                        if (index >= 0) name = name.substr(0, index) + '::' + name.substr(index + 1); // Fast. Simple. Cheat ;)
                        definitions.push(name);
                    }
                }
            } else {
                switch (id) {
                    case 72:
                    case 82:
                        if (id == 82) {
                            this._data.position += 4;
                            this._data.readString(); // identifier
                        }
                        
                        minorVersion = this._data.readUnsignedShort();
                        majorVersion = this._data.readUnsignedShort();
                        if (minorVersion == 0x0010 && majorVersion == 0x002E) definitions.push.apply(definitions, this.getDefinitionNamesInTag(extended));
                    break;
                }
            }

            this._data.position = position + length;
        }

        return definitions;
    }
    
    /**
     * @private
     */
    private function getDefinitionNamesInTag(extended:Boolean):Array {
        var classesOnly:Boolean = !extended;
        var count:int;
        var kind:uint;
        var id:uint;
        var flags:uint;
        var counter:uint;
        var ns:uint;
        var names:Array = new Array();
        this._stringTable = new Array();
        this._namespaceTable = new Array();
        this._multinameTable = new Array();
        
        // int table
        count = this._data.readASInt() - 1;
        
        while (count > 0 && count--) {
            this._data.readASInt();
        }
        
        // uint table
        count = this._data.readASInt() - 1;
        
        while (count > 0 && count--) {
            this._data.readASInt();
        }

        // Double table
        count = this._data.readASInt() - 1;
        
        while (count > 0 && count--) {
            this._data.readDouble();
        }
        
        // String table
        count = this._data.readASInt()-1;
        id = 1;
        
        while (count > 0 && count--) {
            this._stringTable[id] = this._data.readUTFBytes(this._data.readASInt());
            id++;
        }
        
        // Namespace table
        count = this._data.readASInt() - 1;
        id = 1;
        
        while (count > 0 && count--) {
            kind = this._data.readUnsignedByte();
            ns = this._data.readASInt();
            if (kind == 0x16) this._namespaceTable[id] = ns; // only public
            id++;
        }
        
        // NsSet table
        count = this._data.readASInt() - 1;
        
        while (count > 0 && count--) {
             counter = this._data.readUnsignedByte();
             while (counter--) this._data.readASInt();
        }
        
        // Multiname table
        count = this._data.readASInt() - 1;
        id = 1;
        
        while (count > 0 && count--) {
            kind = this._data.readASInt();

            switch (kind) {
                case 0x07:
                case 0x0D:
                    ns = this._data.readASInt();
                    this._multinameTable[id] = [ns, this._data.readASInt()];
                break;    
                case 0x0F:
                case 0x10:
                    this._multinameTable[id] = [0, this._stringTable[this._data.readASInt()]];
                break;    
                case 0x11:
                case 0x12:
                break;    
                case 0x09:
                case 0x0E:
                    this._multinameTable[id] = [0, this._stringTable[this._data.readASInt()]];
                    this._data.readASInt();
                break;    
                case 0x1B:
                case 0x1C:
                    this._data.readASInt();
                break;
                case 0x1D: // Generic
                    if (extended) {
                        var multinameID:uint = this._data.readASInt(); // u8 or u30, maybe YOU know?
                        var params:uint = this._data.readASInt(); // param count (u8 or u30), should always to be 1 in current ABC versions
                        name = this.getName(multinameID);
                        
                        while (params--) {
                            var paramID:uint = this._data.readASInt();
                            
                            if (name) { // not the best method, i know
                                name = name + '.<' + this.getName(paramID) + '>';
                                names.push(name);
                            }
                        }
                        
                        this._multinameTable[id] = [0, name];
                    } else {
                        this._data.readASInt();
                        this._data.readASInt();
                        this._data.readASInt();
                    }
                break;    
            }
            
            id++;
        }
        
        // Method table
        count = this._data.readASInt();

        while (count > 0 && count--) {
            var paramsCount:int = this._data.readASInt();
            counter = paramsCount;
            this._data.readASInt();
            while (counter--) this._data.readASInt();
            this._data.readASInt();
            flags = this._data.readUnsignedByte();
            
            if (flags & 0x08) {
                counter = this._data.readASInt();
                
                while (counter--) {
                    this._data.readASInt();
                    this._data.readASInt();
                }
            }
            
            if (flags & 0x80) {
                counter = paramsCount;
                while (counter--) this._data.readASInt();
            }
        }

        // Metadata table
        count = this._data.readASInt();

        while (count > 0 && count--) {
            this._data.readASInt();
            counter = this._data.readASInt();
            
            while (counter--) {
                this._data.readASInt();
                this._data.readASInt();
            }
        }

        // Instance table
        count = this._data.readASInt();
        var classCount:uint = count;
        var name:String;
        var isInterface:Boolean;

        while (count > 0 && count--) {
            id = this._data.readASInt();
            this._data.readASInt();
            flags = this._data.readUnsignedByte();
            if (flags & 0x08) ns = this._data.readASInt();
            isInterface = Boolean(flags & 0x04);
            counter = this._data.readASInt();
            while (counter--) this._data.readASInt();
            this._data.readASInt(); // iinit
            this.readTraits();
            
            if (classesOnly && !isInterface) {
                name = this.getName(id);
                if (name) names.push(name);
            }
        }
        
        if (classesOnly) return names;
        
        // Class table
        count = classCount;
        
        while (count && count--) {
            this._data.readASInt(); // cinit
            this.readTraits();
        }
        
        // Script table
        count = this._data.readASInt();
        var traits:Array;
        
        while (count && count--) {
            this._data.readASInt(); // init
            traits = this.readTraits(true);
            if (traits.length) names.push.apply(names, traits);
        }

        return names;
    }
    
    /**
     * @private
     */
    private function readTraits(buildNames:Boolean = false):Array {
        var kind:uint;
        var counter:uint;
        var ns:uint;
        var id:uint;
        var traitCount:uint = this._data.readASInt();
        var names:Array;
        var name:String;
        if (buildNames) names = [];

        while (traitCount--) {
            id = this._data.readASInt(); // name
            kind = this._data.readUnsignedByte();
            var upperBits:uint = kind >> 4;
            var lowerBits:uint = kind & 0xF;
            this._data.readASInt();
            this._data.readASInt();
            
            switch (lowerBits) {
                case 0x00:
                case 0x06:
                    if (this._data.readASInt()) this._data.readASInt();
                    break;
            }

            if (buildNames) {
                name = this.getName(id);
                if (name) names.push(name);
            }
            
            if (upperBits & 0x04) {
                counter = this._data.readASInt();
                while (counter--) this._data.readASInt();
            }
        }
        
        return names;
    }
    
    /**
     * @private
     */
    private function getName(id:uint):String {
        if (!(id in this._multinameTable)) return null;
        var mn:Array = this._multinameTable[id] as Array;
        var ns:uint = mn[0] as uint;
        var nsName:String = this._stringTable[this._namespaceTable[ns] as uint] as String;
        var name:String = mn[1] is String ? mn[1] : (this._stringTable[mn[1] as uint] as String);
        if (nsName && nsName.indexOf('__AS3__') < 0 /* cheat! */) name = nsName + '::' + name;
        return name;
    }

}
    
internal class SWFByteArray extends ByteArray {
    
    /**
     * @private
     */
    private static const TAG_SWF:String = 'FWS';
    
    /**
     * @private
     */
    private static const TAG_SWF_COMPRESSED:String = 'CWS';
    
    public function SWFByteArray(data:ByteArray=null):void {
        super();
        super.endian = Endian.LITTLE_ENDIAN;
        var endian:String;
        var tag:String;
        
        if (data) {
            endian = data.endian;
            data.endian = Endian.LITTLE_ENDIAN;
            
            if (data.bytesAvailable > 26) {
                tag = data.readUTFBytes(3);
                
                if (tag == SWFByteArray.TAG_SWF || tag == SWFByteArray.TAG_SWF_COMPRESSED) {
                    this._version = data.readUnsignedByte();
                    data.readUnsignedInt();
                    data.readBytes(this);
                    if (tag == SWFByteArray.TAG_SWF_COMPRESSED) super.uncompress();
                } else throw new ArgumentError('Error #2124: Loaded file is an unknown type.');
                
                this.readHeader();
            }
            
            data.endian = endian;
        }
    }
        
    /**
     * @private
     */
    private var _bitIndex:uint;
    
    /**
     * @private
     */
    private var _version:uint;
    
    public function get version():uint {
        return this._version;
    }
    
    /**
     * @private
     */
    private var _frameRate:Number;
    
    public function get frameRate():Number {
        return this._frameRate;    
    }
    
    /**
     * @private
     */
    private var _rect:Rectangle;
    
    public function get rect():Rectangle {
        return this._rect;
    }

    public function writeBytesFromString(bytesHexString:String):void {
        var length:uint = bytesHexString.length;
        
        for (var i:uint = 0;i<length;i += 2) {
            var hexByte:String = bytesHexString.substr(i, 2);
            var byte:uint = parseInt(hexByte, 16);
            writeByte(byte);
        }
    }
    
    public function readRect():Rectangle {
        var pos:uint = super.position;
        var byte:uint = this[pos];
        var bits:uint = byte >> 3;
        var xMin:Number = this.readBits(bits, 5) / 20;
        var xMax:Number = this.readBits(bits) / 20;
        var yMin:Number = this.readBits(bits) / 20;
        var yMax:Number = this.readBits(bits) / 20;
        super.position = pos + Math.ceil(((bits * 4) - 3) / 8) + 1;
        return new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin);
    }
    
    public function readBits(length:uint, start:int = -1):Number {
        if (start < 0) start = this._bitIndex;
        this._bitIndex = start;
        var byte:uint = this[super.position];
        var out:Number = 0;
        var shift:Number = 0;
        var currentByteBitsLeft:uint = 8 - start;
        var bitsLeft:Number = length - currentByteBitsLeft;
        
        if (bitsLeft > 0) {
            super.position++;
            out = this.readBits(bitsLeft, 0) | ((byte & ((1 << currentByteBitsLeft) - 1)) << (bitsLeft));
        } else {
            out = (byte >> (8 - length - start)) & ((1 << length) - 1);
            this._bitIndex = (start + length) % 8;
            if (start + length > 7) super.position++;
        }
        
        return out;
    }
    
    public function readASInt():int {
        var result:uint = 0;
        var i:uint = 0, byte:uint;
        do {
            byte = super.readUnsignedByte();
            result |= ( byte & 0x7F ) << ( i*7 );
            i+=1;
        } while ( byte & 1<<7 );
        return result;            
    }

    public function readString():String {
        var i:uint = super.position;
        while (this[i] && (i+=1)) {};
        var str:String = super.readUTFBytes(i - super.position);
        super.position = i+1; 
        return str;
    }

    public function traceArray(array:ByteArray):String { // for debug
        var out:String = '';
        var pos:uint = array.position;
        var i:uint = 0;
        array.position = 0;

        while (array.bytesAvailable) {
            var str:String = array.readUnsignedByte().toString(16).toUpperCase();
            str = str.length < 2 ? '0'+str : str;
            out += str+' ';
        }
        
        array.position = pos;
        return out;
    }

    /**
     * @private
     */
    private function readFrameRate():void {
        if (this._version < 8) {
            this._frameRate = super.readUnsignedShort();
        } else {
            var fixed:Number = super.readUnsignedByte() / 0xFF;
            this._frameRate = super.readUnsignedByte() + fixed;
        }
    }
    
    /**
     * @private
     */
    private function readHeader():void {
        this._rect = this.readRect();
        this.readFrameRate();        
        super.readShort(); // num of frames
    }
}