package pt.tumba.parser.swf; import com.anotherbigidea.flash.SWFActionCodes; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Stack; /** * Parse action bytes and drive a SWFActions interface * *@author unknown *@created 15 de Setembro de 2002 */ public class ActionParser implements SWFActionCodes { protected SWFActions actions; protected int blockDepth = 0; /** * Constructor for the ActionParser object * *@param actions Description of the Parameter */ public ActionParser(SWFActions actions) { this.actions = actions; } /** * Description of the Method * *@param bytes Description of the Parameter *@exception IOException Description of the Exception */ public synchronized void parse(byte[] bytes) throws IOException { List records = createRecords(bytes); processRecords(records); } /** * Description of the Method * *@param in Description of the Parameter *@exception IOException Description of the Exception */ public synchronized void parse(InStream in) throws IOException { List records = createRecords(in); processRecords(records); } /** * Description of the Method * *@param records Description of the Parameter *@exception IOException Description of the Exception */ protected void processRecords(List records) throws IOException { //--process action records for (Iterator enumerator = records.iterator(); enumerator.hasNext(); ) { ActionRecord rec = (ActionRecord) enumerator.next(); //actions.comment( "depth=" + rec.blockDepth ); //detect end of block if (rec.blockDepth < blockDepth) { blockDepth--; actions.endBlock(); } if (rec.label != null) { actions.jumpLabel(rec.label); } int code = rec.code; byte[] data = rec.data; InStream in = (data != null && data.length > 0) ? new InStream(data) : null; switch (code) { case 0: actions.end(); break; //--Flash 3 case GOTO_FRAME: actions.gotoFrame(in.readUI16()); break; case GET_URL: actions.getURL(in.readString(), in.readString()); break; case NEXT_FRAME: actions.nextFrame(); break; case PREVIOUS_FRAME: actions.prevFrame(); break; case PLAY: actions.play(); break; case STOP: actions.stop(); break; case TOGGLE_QUALITY: actions.toggleQuality(); break; case STOP_SOUNDS: actions.stopSounds(); break; case WAIT_FOR_FRAME: actions.waitForFrame(in.readUI16(), rec.jumpLabel); break; case SET_TARGET: actions.setTarget(in.readString()); break; case GOTO_LABEL: actions.gotoFrame(in.readString()); break; //--Flash 4 case IF: actions.ifJump(rec.jumpLabel); break; case JUMP: actions.jump(rec.jumpLabel); break; case WAIT_FOR_FRAME_2: actions.waitForFrame(rec.jumpLabel); break; case POP: actions.pop(); break; case PUSH: parsePush(data.length, in); break; case ADD: actions.add(); break; case SUBTRACT: actions.substract(); break; case MULTIPLY: actions.multiply(); break; case DIVIDE: actions.divide(); break; case EQUALS: actions.equals(); break; case LESS: actions.lessThan(); break; case AND: actions.and(); break; case OR: actions.or(); break; case NOT: actions.not(); break; case STRING_EQUALS: actions.stringEquals(); break; case STRING_LENGTH: actions.stringLength(); break; case STRING_ADD: actions.concat(); break; case STRING_EXTRACT: actions.substring(); break; case STRING_LESS: actions.stringLessThan(); break; case MB_STRING_EXTRACT: actions.substringMB(); break; case MB_STRING_LENGTH: actions.stringLengthMB(); break; case TO_INTEGER: actions.toInteger(); break; case CHAR_TO_ASCII: actions.charToAscii(); break; case ASCII_TO_CHAR: actions.asciiToChar(); break; case MB_CHAR_TO_ASCII: actions.charMBToAscii(); break; case MB_ASCII_TO_CHAR: actions.asciiToCharMB(); break; case CALL: actions.call(); break; case GET_VARIABLE: actions.getVariable(); break; case SET_VARIABLE: actions.setVariable(); break; case GET_URL_2: parseGetURL2(in.readUI8()); break; case GOTO_FRAME_2: actions.gotoFrame(in.readUI8() != 0); break; case SET_TARGET_2: actions.setTarget(); break; case GET_PROPERTY: actions.getProperty(); break; case SET_PROPERTY: actions.setProperty(); break; case CLONE_SPRITE: actions.cloneSprite(); break; case REMOVE_SPRITE: actions.removeSprite(); break; case START_DRAG: actions.startDrag(); break; case END_DRAG: actions.endDrag(); break; case TRACE: actions.trace(); break; case GET_TIME: actions.getTime(); break; case RANDOM_NUMBER: actions.randomNumber(); break; //--Flash 5 case INIT_ARRAY: actions.initArray(); break; case LOOKUP_TABLE: parseLookupTable(in); break; case CALL_FUNCTION: actions.callFunction(); break; case CALL_METHOD: actions.callMethod(); break; case DEFINE_FUNCTION: parseDefineFunction(in); break; case DEFINE_LOCAL_VAL: actions.defineLocalValue(); break; case DEFINE_LOCAL: actions.defineLocal(); break; case DEL_VAR: actions.deleteProperty(); break; case DEL_THREAD_VARS: actions.deleteThreadVars(); break; case ENUMERATE: actions.enumerate(); break; case TYPED_EQUALS: actions.typedEquals(); break; case GET_MEMBER: actions.getMember(); break; case INIT_OBJECT: actions.initObject(); break; case CALL_NEW_METHOD: actions.newMethod(); break; case NEW_OBJECT: actions.newObject(); break; case SET_MEMBER: actions.setMember(); break; case GET_TARGET_PATH: actions.getTargetPath(); break; case WITH: parseWith(in); break; case DUPLICATE: actions.duplicate(); break; case RETURN: actions.returnValue(); break; case SWAP: actions.swap(); break; case REGISTER: actions.storeInRegister(in.readUI8()); break; case MODULO: actions.modulo(); break; case TYPEOF: actions.typeOf(); break; case TYPED_ADD: actions.typedAdd(); break; case TYPED_LESS_THAN: actions.typedLessThan(); break; case CONVERT_TO_NUMBER: actions.convertToNumber(); break; case CONVERT_TO_STRING: actions.convertToString(); break; case INCREMENT: actions.increment(); break; case DECREMENT: actions.decrement(); break; case BIT_AND: actions.bitAnd(); break; case BIT_OR: actions.bitOr(); break; case BIT_XOR: actions.bitXor(); break; case SHIFT_LEFT: actions.shiftLeft(); break; case SHIFT_RIGHT: actions.shiftRight(); break; case SHIFT_UNSIGNED: actions.shiftRightUnsigned(); break; default: actions.unknown(code, data); break; } } } /** * Description of the Method * *@param in Description of the Parameter *@exception IOException Description of the Exception */ protected void parseDefineFunction(InStream in) throws IOException { String name = in.readString(); int paramCount = in.readUI16(); String[] params = new String[paramCount]; for (int i = 0; i < params.length; i++) { params[i] = in.readString(); } //int codesize = in.readUI16(); //System.out.println( "codesize=" + codesize ); System.out.flush(); actions.startFunction(name, params); blockDepth++; } /** * Description of the Method * *@param in Description of the Parameter *@exception IOException Description of the Exception */ protected void parseWith(InStream in) throws IOException { // int codesize = in.readUI16(); actions.startWith(); blockDepth++; } /** * Description of the Method * *@param in Description of the Parameter *@exception IOException Description of the Exception */ protected void parseLookupTable(InStream in) throws IOException { String[] strings = new String[in.readUI16()]; for (int i = 0; i < strings.length; i++) { strings[i] = in.readString(); } actions.lookupTable(strings); } /** * Description of the Method * *@param flags Description of the Parameter *@exception IOException Description of the Exception */ protected void parseGetURL2(int flags) throws IOException { int sendVars = flags & 0x03; int mode = 0; switch (flags & 0xF0) { case 0x40: mode = SWFActions.GET_URL_MODE_LOAD_MOVIE_INTO_SPRITE; break; case 0x80: mode = SWFActions.GET_URL_MODE_LOAD_VARS_INTO_LEVEL; break; case 0xC0: mode = SWFActions.GET_URL_MODE_LOAD_VARS_INTO_SPRITE; break; default: mode = SWFActions.GET_URL_MODE_LOAD_MOVIE_INTO_LEVEL; break; } actions.getURL(sendVars, mode); } /** * Description of the Method * *@param length Description of the Parameter *@param in Description of the Parameter *@exception IOException Description of the Exception */ protected void parsePush(int length, InStream in) throws IOException { while (in.getBytesRead() < length) { int pushType = in.readUI8(); switch (pushType) { case PUSHTYPE_STRING: actions.push(in.readString()); break; case PUSHTYPE_FLOAT: actions.push(in.readFloat()); break; case PUSHTYPE_NULL: actions.pushNull(); break; case PUSHTYPE_03: break; case PUSHTYPE_REGISTER: actions.pushRegister(in.readUI8()); break; case PUSHTYPE_BOOLEAN: actions.push((in.readUI8() != 0) ? true : false); break; case PUSHTYPE_DOUBLE: actions.push(in.readDouble()); break; case PUSHTYPE_INTEGER: actions.push(in.readSI32()); break; case PUSHTYPE_LOOKUP: actions.lookup(in.readUI8()); break; default: } } } /** * Description of the Class * *@author unknown *@created 15 de Setembro de 2002 */ protected static class ActionRecord { /** * Description of the Field */ public int offset; //byte offset from start of the action array /** * Description of the Field */ public int code; public String label; public String jumpLabel; public byte[] data; public int blockDepth = 0; /** * Constructor for the ActionRecord object * *@param offset Description of the Parameter *@param code Description of the Parameter *@param data Description of the Parameter */ protected ActionRecord(int offset, int code, byte[] data) { this.offset = offset; this.code = code; this.data = data; } } /** * First Pass to determine action offsets and jumps * *@param bytes Description of the Parameter *@return Description of the Return Value *@exception IOException Description of the Exception */ protected List createRecords(byte[] bytes) throws IOException { return createRecords(new InStream(bytes)); } /** * First Pass to determine action offsets and jumps * *@param in Description of the Parameter *@return Description of the Return Value *@exception IOException Description of the Exception */ protected List createRecords(InStream in) throws IOException { List records = new ArrayList(); List<ActionRecord> jumpers = new ArrayList(); List<Integer> skippers = new ArrayList(); HashMap offsetTable = new HashMap(); Stack blockSizes = new Stack(); int labelIndex = 0; while (true) { int offset = (int) in.getBytesRead(); //System.out.println( "read=" + offset ); System.out.flush(); int code = in.readUI8(); int dataLength = (code >= 0x80) ? in.readUI16() : 0; byte[] data = (dataLength > 0) ? in.read(dataLength) : null; //System.out.println( "size=" + dataLength ); System.out.flush(); ActionRecord rec = new ActionRecord(offset, code, data); records.add(rec); offsetTable.put(new Integer(offset), rec); if (!blockSizes.isEmpty()) { int depth = blockSizes.size(); rec.blockDepth = depth; int blockDecrement = (dataLength > 0) ? (dataLength + 3) : 1; //--subtract the size of this action from all the block sizes // in the block stack for (int i = depth - 1; i >= 0; i--) { int[] blockSize = (int[]) blockSizes.elementAt(i); int size = blockSize[0]; size -= blockDecrement; //--reached end of block ? if (size <= 0) { blockSizes.pop(); } else { blockSize[0] = size; } } } if (code == 0) { break; } //end of actions else if (code == DEFINE_FUNCTION) { InStream in2 = new InStream(rec.data); in2.readString(); int params = in2.readUI16(); for (int i = 0; i < params; i++) { in2.readString(); } int blockSize = in2.readUI16(); blockSizes.push(new int[]{blockSize}); } else if (code == WITH) { InStream in2 = new InStream(rec.data); int blockSize = in2.readUI16(); blockSizes.push(new int[]{blockSize}); } else if (code == WAIT_FOR_FRAME || code == WAIT_FOR_FRAME_2) { skippers.add(new Integer(records.size() - 1)); } else if (code == IF || code == JUMP) { jumpers.add(rec); } } //--Tie up the jumpers with the offsets for (ActionRecord rec : jumpers) { InStream in2 = new InStream(rec.data); int jumpOffset = in2.readSI16(); int offset = rec.offset + 5; int absoluteOffset = offset + jumpOffset; ActionRecord target = (ActionRecord) offsetTable.get(new Integer(absoluteOffset)); if (target != null) { if (target.label == null) { target.label = rec.jumpLabel = "label" + (labelIndex++); } else { rec.jumpLabel = target.label; } } } //--Tie up the skippers with labels for (Integer idx : skippers) { ActionRecord rec = (ActionRecord) records.get(idx); InStream in2 = new InStream(rec.data); if (rec.code == WAIT_FOR_FRAME) { in2.readUI16(); } //skip frame number int skip = in2.readUI8(); int skipIndex = idx + skip + 1; if (skipIndex < records.size()) { ActionRecord target = (ActionRecord) records.get(skipIndex); if (target.label == null) { target.label = rec.jumpLabel = "label" + (labelIndex++); } else { rec.jumpLabel = target.label; } } } return records; } }