Source code: com/anotherbigidea/flash/readers/ActionParser.java
1 /****************************************************************
2 * Copyright (c) 2001, David N. Main, All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the
6 * following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
16 *
17 * 3. The name of the author may not be used to endorse or
18 * promote products derived from this software without specific
19 * prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
23 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
31 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
32 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 ****************************************************************/
34 package com.anotherbigidea.flash.readers;
35
36 import java.io.IOException;
37 import java.util.Enumeration;
38 import java.util.Hashtable;
39 import java.util.Stack;
40 import java.util.Vector;
41
42 import com.anotherbigidea.flash.SWFActionCodes;
43 import com.anotherbigidea.flash.SWFConstants;
44 import com.anotherbigidea.flash.interfaces.SWFActions;
45 import com.anotherbigidea.io.InStream;
46
47 /**
48 * Parse action bytes and drive a SWFActions interface
49 */
50 public class ActionParser implements SWFActionCodes
51 {
52 protected SWFActions actions;
53 protected int blockDepth = 0;
54 protected String mStringEncoding = SWFConstants.STRING_ENCODING_MX;
55
56 public ActionParser( SWFActions actions, int flashVersion )
57 {
58 this.actions = actions;
59 if( flashVersion < SWFConstants.FLASH_MX_VERSION ) {
60 mStringEncoding = SWFConstants.STRING_ENCODING_PRE_MX;
61 }
62 }
63
64 public synchronized void parse( byte[] bytes ) throws IOException
65 {
66 Vector records = createRecords( bytes );
67 processRecords( records );
68 }
69
70 public synchronized void parse( InStream in ) throws IOException
71 {
72 Vector records = createRecords( in );
73 processRecords( records );
74 }
75
76 protected void processRecords( Vector records ) throws IOException
77 {
78 //--process action records
79 for( Enumeration enum = records.elements(); enum.hasMoreElements(); )
80 {
81 ActionRecord rec = (ActionRecord)enum.nextElement();
82
83 //actions.comment( "depth=" + rec.blockDepth );
84
85 //detect end of block
86 if( rec.blockDepth < blockDepth )
87 {
88 blockDepth--;
89 actions.endBlock();
90 }
91
92 if( rec.label != null ) actions.jumpLabel( rec.label );
93
94 int code = rec.code;
95 byte[] data = rec.data;
96
97 InStream in = (data!=null && data.length > 0) ? new InStream(data) : null;
98
99 switch( code )
100 {
101 case 0: actions.end(); break;
102
103 //--Flash 3
104 case GOTO_FRAME : actions.gotoFrame( in.readUI16() ); break;
105 case GET_URL : actions.getURL( in.readString(mStringEncoding), in.readString(mStringEncoding) ); break;
106 case NEXT_FRAME : actions.nextFrame(); break;
107 case PREVIOUS_FRAME: actions.prevFrame(); break;
108 case PLAY : actions.play(); break;
109 case STOP : actions.stop(); break;
110 case TOGGLE_QUALITY: actions.toggleQuality(); break;
111 case STOP_SOUNDS : actions.stopSounds(); break;
112 case WAIT_FOR_FRAME: actions.waitForFrame( in.readUI16(), rec.jumpLabel ); break;
113 case SET_TARGET : actions.setTarget( in.readString(mStringEncoding) ); break;
114 case GOTO_LABEL : actions.gotoFrame( in.readString(mStringEncoding) ); break;
115
116 //--Flash 4
117 case IF : actions.ifJump( rec.jumpLabel ); break;
118 case JUMP : actions.jump( rec.jumpLabel ); break;
119 case WAIT_FOR_FRAME_2: actions.waitForFrame( rec.jumpLabel ); break;
120 case POP : actions.pop(); break;
121 case PUSH : parsePush( data.length, in ); break;
122 case ADD : actions.add(); break;
123 case SUBTRACT : actions.substract(); break;
124 case MULTIPLY : actions.multiply(); break;
125 case DIVIDE : actions.divide(); break;
126
127 case EQUALS : actions.equals(); break;
128 case LESS : actions.lessThan(); break;
129 case AND : actions.and(); break;
130 case OR : actions.or(); break;
131 case NOT : actions.not(); break;
132
133 case STRING_EQUALS : actions.stringEquals(); break;
134 case STRING_LENGTH : actions.stringLength(); break;
135 case STRING_ADD : actions.concat(); break;
136 case STRING_EXTRACT : actions.substring(); break;
137 case STRING_LESS : actions.stringLessThan(); break;
138 case MB_STRING_EXTRACT : actions.substringMB(); break;
139 case MB_STRING_LENGTH : actions.stringLengthMB(); break;
140
141 case TO_INTEGER : actions.toInteger(); break;
142 case CHAR_TO_ASCII : actions.charToAscii(); break;
143 case ASCII_TO_CHAR : actions.asciiToChar(); break;
144 case MB_CHAR_TO_ASCII : actions.charMBToAscii(); break;
145 case MB_ASCII_TO_CHAR : actions.asciiToCharMB(); break;
146
147 case CALL : actions.call(); break;
148 case GET_VARIABLE : actions.getVariable(); break;
149 case SET_VARIABLE : actions.setVariable(); break;
150
151 case GET_URL_2 : parseGetURL2( in.readUI8() ); break;
152
153 case GOTO_FRAME_2 : actions.gotoFrame( in.readUI8() != 0 ); break;
154 case SET_TARGET_2 : actions.setTarget(); break;
155 case GET_PROPERTY : actions.getProperty(); break;
156 case SET_PROPERTY : actions.setProperty(); break;
157 case CLONE_SPRITE : actions.cloneSprite(); break;
158 case REMOVE_SPRITE : actions.removeSprite(); break;
159 case START_DRAG : actions.startDrag(); break;
160 case END_DRAG : actions.endDrag(); break;
161 case TRACE : actions.trace(); break;
162 case GET_TIME : actions.getTime(); break;
163 case RANDOM_NUMBER : actions.randomNumber(); break;
164
165 //--Flash 5
166 case INIT_ARRAY : actions.initArray(); break;
167 case LOOKUP_TABLE : parseLookupTable( in ); break;
168 case CALL_FUNCTION : actions.callFunction(); break;
169 case CALL_METHOD : actions.callMethod(); break;
170 case DEFINE_FUNCTION : parseDefineFunction(in); break;
171 case DEFINE_LOCAL_VAL : actions.defineLocalValue(); break;
172 case DEFINE_LOCAL : actions.defineLocal(); break;
173 case DEL_VAR : actions.deleteProperty(); break;
174 case DEL_THREAD_VARS : actions.deleteThreadVars(); break;
175 case ENUMERATE : actions.enumerate(); break;
176 case TYPED_EQUALS : actions.typedEquals(); break;
177 case GET_MEMBER : actions.getMember(); break;
178 case INIT_OBJECT : actions.initObject(); break;
179 case CALL_NEW_METHOD : actions.newMethod(); break;
180 case NEW_OBJECT : actions.newObject(); break;
181 case SET_MEMBER : actions.setMember(); break;
182 case GET_TARGET_PATH : actions.getTargetPath(); break;
183 case WITH : parseWith( in ); break;
184 case DUPLICATE : actions.duplicate(); break;
185 case RETURN : actions.returnValue(); break;
186 case SWAP : actions.swap(); break;
187 case REGISTER : actions.storeInRegister( in.readUI8() ); break;
188 case MODULO : actions.modulo(); break;
189 case TYPEOF : actions.typeOf(); break;
190 case TYPED_ADD : actions.typedAdd(); break;
191 case TYPED_LESS_THAN : actions.typedLessThan(); break;
192 case CONVERT_TO_NUMBER : actions.convertToNumber(); break;
193 case CONVERT_TO_STRING : actions.convertToString(); break;
194 case INCREMENT : actions.increment(); break;
195 case DECREMENT : actions.decrement(); break;
196 case BIT_AND : actions.bitAnd(); break;
197 case BIT_OR : actions.bitOr(); break;
198 case BIT_XOR : actions.bitXor(); break;
199 case SHIFT_LEFT : actions.shiftLeft(); break;
200 case SHIFT_RIGHT : actions.shiftRight(); break;
201 case SHIFT_UNSIGNED : actions.shiftRightUnsigned(); break;
202
203 //--Flash 6
204 case INSTANCE_OF : actions.instanceOf(); break;
205 case ENUMERATE_OBJECT : actions.enumerateObject(); break;
206 case GREATER : actions.greaterThan(); break;
207 case STRICT_EQUALS : actions.strictEquals(); break;
208 case STRING_GREATER : actions.stringGreaterThan(); break;
209
210 default: actions.unknown( code, data ); break;
211 }
212 }
213 }
214
215 protected void parseDefineFunction( InStream in ) throws IOException
216 {
217 String name = in.readString(mStringEncoding);
218 int paramCount = in.readUI16();
219
220 String[] params = new String[ paramCount ];
221 for( int i = 0; i < params.length; i++ )
222 {
223 params[i] = in.readString(mStringEncoding);
224 }
225
226 int codesize = in.readUI16();
227
228 //System.out.println( "codesize=" + codesize ); System.out.flush();
229
230 actions.startFunction( name, params );
231
232 //empty functions have no code
233 if( codesize == 0 ) {
234 actions.endBlock();
235 return;
236 }
237
238 blockDepth++;
239 }
240
241 protected void parseWith( InStream in ) throws IOException
242 {
243 int codesize = in.readUI16();
244
245 actions.startWith( );
246
247 //empty blocks have no code
248 if( codesize == 0 ) {
249 actions.endBlock();
250 return;
251 }
252
253 blockDepth++;
254 }
255
256 protected void parseLookupTable( InStream in ) throws IOException
257 {
258 String[] strings = new String[ in.readUI16() ];
259
260 for( int i = 0; i < strings.length; i++ )
261 {
262 strings[i] = in.readString(mStringEncoding);
263 }
264
265 actions.lookupTable( strings );
266 }
267
268 protected void parseGetURL2( int flags ) throws IOException
269 {
270 int sendVars = flags & 0x03;
271 int mode = 0;
272
273 switch( flags & 0xF0 )
274 {
275 case 0x40: mode = SWFActions.GET_URL_MODE_LOAD_MOVIE_INTO_SPRITE; break;
276 case 0x80: mode = SWFActions.GET_URL_MODE_LOAD_VARS_INTO_LEVEL; break;
277 case 0xC0: mode = SWFActions.GET_URL_MODE_LOAD_VARS_INTO_SPRITE; break;
278 default: mode = SWFActions.GET_URL_MODE_LOAD_MOVIE_INTO_LEVEL; break;
279 }
280
281 actions.getURL( sendVars, mode );
282 }
283
284 protected void parsePush( int length, InStream in ) throws IOException
285 {
286 while( in.getBytesRead() < length )
287 {
288 int pushType = in.readUI8();
289
290 switch( pushType )
291 {
292 case PUSHTYPE_STRING : actions.push( in.readString(mStringEncoding) ); break;
293 case PUSHTYPE_FLOAT : actions.push( in.readFloat() ); break;
294 case PUSHTYPE_NULL : actions.pushNull(); break;
295 case PUSHTYPE_03 : break;
296 case PUSHTYPE_REGISTER: actions.pushRegister( in.readUI8() ); break;
297 case PUSHTYPE_BOOLEAN : actions.push( (in.readUI8() != 0) ? true : false ); break;
298 case PUSHTYPE_DOUBLE : actions.push( in.readDouble() ); break;
299 case PUSHTYPE_INTEGER : actions.push( in.readSI32() ); break;
300 case PUSHTYPE_LOOKUP : actions.lookup( in.readUI8() ); break;
301 default:
302 }
303 }
304 }
305
306 protected static class ActionRecord
307 {
308 public int offset; //byte offset from start of the action array
309 public int code;
310 public String label;
311 public String jumpLabel;
312 public byte[] data;
313 public int blockDepth = 0;
314
315 protected ActionRecord( int offset, int code, byte[] data )
316 {
317 this.offset = offset;
318 this.code = code;
319 this.data = data;
320 }
321 }
322
323 /**
324 * First Pass to determine action offsets and jumps
325 */
326 protected Vector createRecords( byte[] bytes ) throws IOException
327 {
328 return createRecords( new InStream( bytes ));
329 }
330
331 /**
332 * First Pass to determine action offsets and jumps
333 */
334 protected Vector createRecords( InStream in ) throws IOException
335 {
336 Vector records = new Vector();
337 Vector jumpers = new Vector();
338 Vector skippers = new Vector();
339 Hashtable offsetTable = new Hashtable();
340
341 Stack blockSizes = new Stack();
342
343 int labelIndex = 0;
344
345 while( true )
346 {
347 int offset = (int)in.getBytesRead();
348
349 //System.out.println( "read=" + offset ); System.out.flush();
350
351 int code = in.readUI8();
352 int dataLength = (code >= 0x80) ? in.readUI16() : 0;
353 byte[] data = ( dataLength > 0 ) ? in.read( dataLength ) : null;
354
355 //System.out.println( "size=" + dataLength ); System.out.flush();
356
357 ActionRecord rec = new ActionRecord( offset, code, data );
358 records.addElement( rec );
359 offsetTable.put( new Integer(offset), rec );
360
361 if( ! blockSizes.isEmpty() )
362 {
363 int depth = blockSizes.size();
364 rec.blockDepth = depth;
365 int blockDecrement = ( dataLength > 0 ) ? ( dataLength + 3 ) : 1;
366
367 //--subtract the size of this action from all the block sizes
368 // in the block stack
369 for( int i = depth-1; i >= 0; i-- )
370 {
371 int[] blockSize = (int[])blockSizes.elementAt(i);
372 int size = blockSize[0];
373
374 size -= blockDecrement;
375
376 //--reached end of block ?
377 if( size <= 0 ) blockSizes.pop();
378 else blockSize[0] = size;
379 }
380 }
381
382 if( code == 0 ) break; //end of actions
383
384 else if( code == DEFINE_FUNCTION )
385 {
386 InStream in2 = new InStream( rec.data );
387 in2.readString(mStringEncoding);
388 int params = in2.readUI16();
389 for( int i = 0; i < params; i++ ) in2.readString(mStringEncoding);
390 int blockSize = in2.readUI16();
391 blockSizes.push( new int[]{ blockSize } );
392 }
393 else if( code == WITH )
394 {
395 InStream in2 = new InStream( rec.data );
396 int blockSize = in2.readUI16();
397 blockSizes.push( new int[]{ blockSize } );
398 }
399 else if( code == WAIT_FOR_FRAME || code == WAIT_FOR_FRAME_2 )
400 {
401 skippers.addElement( new Integer(records.size()-1));
402 }
403 else if( code == IF || code == JUMP ) jumpers.addElement( rec );
404 }
405
406 //--Tie up the jumpers with the offsets
407 for( Enumeration enum = jumpers.elements(); enum.hasMoreElements(); )
408 {
409 ActionRecord rec = (ActionRecord)enum.nextElement();
410
411 InStream in2 = new InStream( rec.data );
412 int jumpOffset = in2.readSI16();
413 int offset = rec.offset + 5;
414 int absoluteOffset = offset + jumpOffset;
415
416 ActionRecord target =
417 (ActionRecord)offsetTable.get( new Integer(absoluteOffset) );
418
419 if( target != null )
420 {
421 if( target.label == null ) target.label = rec.jumpLabel = "label" + (labelIndex++);
422 else rec.jumpLabel = target.label;
423 }
424 }
425
426 //--Tie up the skippers with labels
427 for( Enumeration enum = skippers.elements(); enum.hasMoreElements(); )
428 {
429 int idx = ((Integer)enum.nextElement()).intValue();
430
431 ActionRecord rec = (ActionRecord)records.elementAt(idx);
432
433 InStream in2 = new InStream( rec.data );
434
435 if( rec.code == WAIT_FOR_FRAME ) in2.readUI16(); //skip frame number
436 int skip = in2.readUI8();
437 int skipIndex = idx + skip + 1;
438
439 if( skipIndex < records.size() )
440 {
441 ActionRecord target = (ActionRecord)records.elementAt(skipIndex);
442
443 if( target.label == null ) target.label = rec.jumpLabel = "label" + (labelIndex++);
444 else rec.jumpLabel = target.label;
445 }
446 }
447
448 return records;
449 }
450
451
452 }